├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── pull_request_template.md ├── stale.yml └── workflows │ └── ci.yaml ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── _version └── version.go ├── annotate ├── backend │ ├── backend.go │ ├── elastic2.go │ ├── elastic5.go │ ├── elastic6.go │ └── elastic7.go ├── client.go ├── cmd │ └── annotate │ │ ├── main.go │ │ ├── os_unix.go │ │ └── os_windows.go ├── mk_rpm_fpmdir.annotate_cli.txt ├── mk_rpm_fpmdir.annotate_server.txt ├── models.go └── web │ ├── README.md │ ├── config.toml │ ├── static.go │ ├── static │ ├── css │ │ ├── bootstrap.min.css │ │ └── jquery-ui.min.css │ ├── favicon.ico │ ├── index.html │ ├── js │ │ ├── angular-route.min.js │ │ ├── angular-strap.js │ │ ├── angular-strap.tpl.min.js │ │ ├── angular.min.js │ │ ├── annotate.js │ │ ├── annotate.ts │ │ ├── bootstrap.min.js │ │ ├── jquery-ui.js │ │ ├── jquery.min.js │ │ ├── moment-duration-format.min.js │ │ ├── moment.min.js │ │ ├── typings.json │ │ ├── typings │ │ │ ├── browser.d.ts │ │ │ ├── browser │ │ │ │ ├── ambient │ │ │ │ │ ├── angular │ │ │ │ │ │ └── angular.d.ts │ │ │ │ │ ├── jquery │ │ │ │ │ │ └── jquery.d.ts │ │ │ │ │ └── underscore │ │ │ │ │ │ └── underscore.d.ts │ │ │ │ └── definitions │ │ │ │ │ └── moment │ │ │ │ │ └── moment.d.ts │ │ │ ├── main.d.ts │ │ │ └── main │ │ │ │ ├── ambient │ │ │ │ ├── angular │ │ │ │ │ └── angular.d.ts │ │ │ │ ├── jquery │ │ │ │ │ └── jquery.d.ts │ │ │ │ └── underscore │ │ │ │ │ └── underscore.d.ts │ │ │ │ └── definitions │ │ │ │ └── moment │ │ │ │ └── moment.d.ts │ │ └── underscore-min.js │ └── partials │ │ ├── create.html │ │ └── list.html │ └── web.go ├── build.scollector.sh ├── build.tsdbrelay.sh ├── build ├── build.go ├── dockerRelease.sh ├── generate │ └── generate.go ├── release.sh ├── release │ └── githubRelease.go ├── syncTokens │ └── main.go ├── test-startup.sh └── validate.sh ├── cloudwatch ├── cloudwatch.go └── cloudwatch_test.go ├── cmd ├── backfill │ └── main.go ├── bosun │ ├── README.md │ ├── bosun.example.toml │ ├── cache │ │ └── cache.go │ ├── conf │ │ ├── actionNotify.go │ │ ├── bytesize.go │ │ ├── conf.go │ │ ├── conf_test.go │ │ ├── lookup.go │ │ ├── notify.go │ │ ├── rule │ │ │ ├── invalid │ │ │ │ ├── crit-notification-no-template │ │ │ │ ├── crit-warn-unmatching-tags │ │ │ │ ├── depends-no-overlap │ │ │ │ ├── log-no-notification │ │ │ │ ├── lookup-key-pairs │ │ │ │ ├── lookup-key-pairs-dup │ │ │ │ ├── notification-lookup-bad-notification │ │ │ │ ├── notification-lookup-table │ │ │ │ └── number-func-args │ │ │ ├── loaders.go │ │ │ ├── modify.go │ │ │ ├── parse │ │ │ │ ├── lex.go │ │ │ │ ├── node.go │ │ │ │ ├── parse.go │ │ │ │ ├── parse_test.go │ │ │ │ ├── test_invalid │ │ │ │ │ ├── 1 │ │ │ │ │ ├── 2 │ │ │ │ │ ├── 3 │ │ │ │ │ └── 4 │ │ │ │ └── test_valid │ │ │ │ │ ├── 1 │ │ │ │ │ ├── 2 │ │ │ │ │ ├── 3 │ │ │ │ │ └── 4 │ │ │ ├── rule.go │ │ │ ├── rule_test.go │ │ │ └── test.conf │ │ ├── system.go │ │ ├── system_elastic2.go │ │ ├── system_elastic5.go │ │ ├── system_elastic6.go │ │ ├── system_elastic7.go │ │ ├── system_elastic_all.go │ │ ├── system_test.go │ │ ├── template │ │ │ └── template.go │ │ ├── test.toml │ │ └── unknownNotify.go │ ├── database │ │ ├── config_data.go │ │ ├── database.go │ │ ├── error_data.go │ │ ├── metric_metadata.go │ │ ├── migrate.go │ │ ├── models.go │ │ ├── notification_data.go │ │ ├── search_data.go │ │ ├── sentinel │ │ │ └── sentinel.go │ │ ├── silence_data.go │ │ ├── state_data.go │ │ ├── tag_metadata.go │ │ └── test │ │ │ ├── config_test.go │ │ │ ├── database_test.go │ │ │ ├── errors_test.go │ │ │ ├── metric_metadata_test.go │ │ │ ├── notifications_test.go │ │ │ ├── search_data_test.go │ │ │ ├── silence_test.go │ │ │ ├── tag_metadata_test.go │ │ │ ├── testSetup.go │ │ │ └── util │ │ │ └── purge_search_data.go │ ├── dev.sample.conf │ ├── expr │ │ ├── annotate.go │ │ ├── azure.go │ │ ├── azureai.go │ │ ├── cloudwatch.go │ │ ├── cloudwatch_test.go │ │ ├── elastic.go │ │ ├── elastic2.go │ │ ├── elastic5.go │ │ ├── elastic6.go │ │ ├── elastic7.go │ │ ├── elastic_all.go │ │ ├── expr.go │ │ ├── expr_test.go │ │ ├── funcs.go │ │ ├── funcs_test.go │ │ ├── graphite.go │ │ ├── influx.go │ │ ├── influx_test.go │ │ ├── map_test.go │ │ ├── parse │ │ │ ├── lex.go │ │ │ ├── lex_test.go │ │ │ ├── node.go │ │ │ ├── parse.go │ │ │ └── parse_test.go │ │ ├── perf_test.go │ │ ├── prom.go │ │ ├── prom_test.go │ │ └── tsdb.go │ ├── main.go │ ├── ping │ │ └── ping.go │ ├── sched │ │ ├── alertRunner.go │ │ ├── chart.go │ │ ├── check.go │ │ ├── check_test.go │ │ ├── depends_test.go │ │ ├── esquery2.go │ │ ├── esquery5.go │ │ ├── esquery6.go │ │ ├── esquery7.go │ │ ├── grouping_test.go │ │ ├── host.go │ │ ├── notification_test.go │ │ ├── notify.go │ │ ├── sched.go │ │ ├── sched_test.go │ │ ├── silence.go │ │ ├── slack │ │ │ └── slack.go │ │ ├── template.go │ │ └── views.go │ ├── search │ │ ├── search.go │ │ └── search_test.go │ ├── syslog_unix.go │ ├── w.sh │ └── web │ │ ├── chart.go │ │ ├── embed.go │ │ ├── expr.go │ │ ├── incident.go │ │ ├── middlewares.go │ │ ├── relay_test.go │ │ ├── roles.go │ │ ├── roles_test.go │ │ ├── save.go │ │ ├── search.go │ │ ├── static.go │ │ ├── static │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ ├── font-awesome.min.css │ │ │ ├── images │ │ │ │ └── ui-icons_222222_256x240.png │ │ │ ├── jquery-ui.min.css │ │ │ ├── rickshaw.min.css │ │ │ └── variables.less │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── img │ │ │ ├── tsaf-logo-mark-noborder.svg │ │ │ ├── tsaf-logo-mark.png │ │ │ ├── tsaf-logo-mark.svg │ │ │ ├── tsaf-logo-small.png │ │ │ ├── tsaf-logo-text.png │ │ │ └── tsaf-logo.png │ │ ├── js │ │ │ ├── .editorconfig │ │ │ ├── 0-bosun.ts │ │ │ ├── FileSaver.min.js │ │ │ ├── ace │ │ │ │ ├── ace.js │ │ │ │ ├── ext-searchbox.js │ │ │ │ ├── mode-bosun.js │ │ │ │ └── theme-chrome.js │ │ │ ├── action.ts │ │ │ ├── angular-ace.js │ │ │ ├── angular-route.d.ts │ │ │ ├── angular-route.min.js │ │ │ ├── angular-route.min.js.map │ │ │ ├── angular-sanitize.d.ts │ │ │ ├── angular-sanitize.min.js │ │ │ ├── angular-sanitize.min.js.map │ │ │ ├── angular-strap.js │ │ │ ├── angular-strap.min.js.map │ │ │ ├── angular-strap.tpl.min.js │ │ │ ├── angular.d.ts │ │ │ ├── angular.min.js │ │ │ ├── angular.min.js.map │ │ │ ├── annotation.ts │ │ │ ├── authService.ts │ │ │ ├── bootstrap.d.ts │ │ │ ├── bootstrap.min.js │ │ │ ├── bosun.js │ │ │ ├── clipboard.min.js │ │ │ ├── config.ts │ │ │ ├── d3.v3.min.js │ │ │ ├── dashboard.ts │ │ │ ├── directives.ts │ │ │ ├── errors.ts │ │ │ ├── expr.ts │ │ │ ├── graph.ts │ │ │ ├── history.ts │ │ │ ├── host.ts │ │ │ ├── incident.ts │ │ │ ├── items.ts │ │ │ ├── jquery-ui.js │ │ │ ├── jquery.d.ts │ │ │ ├── jquery.min.js │ │ │ ├── jquery.tablesorter.min.js │ │ │ ├── linkService.ts │ │ │ ├── models.ts │ │ │ ├── moment-duration-format.d.ts │ │ │ ├── moment-duration-format.min.js │ │ │ ├── moment.d.ts │ │ │ ├── moment.min.js │ │ │ ├── ngclipboard.min.js │ │ │ ├── put.ts │ │ │ ├── silence.ts │ │ │ ├── state.ts │ │ │ ├── tokenList.ts │ │ │ ├── tokenNew.ts │ │ │ ├── underscore-min.js │ │ │ ├── underscore-min.map │ │ │ └── underscore.d.ts │ │ ├── partials │ │ │ ├── ack.html │ │ │ ├── ackgroup.html │ │ │ ├── action.html │ │ │ ├── alerthistory.html │ │ │ ├── alertstate.html │ │ │ ├── annotation.html │ │ │ ├── cancelClose.html │ │ │ ├── close.html │ │ │ ├── computations.html │ │ │ ├── config.html │ │ │ ├── dashboard.html │ │ │ ├── errors.html │ │ │ ├── expr.html │ │ │ ├── forceClose.html │ │ │ ├── forget.html │ │ │ ├── graph.html │ │ │ ├── history.html │ │ │ ├── host.html │ │ │ ├── incident.html │ │ │ ├── items.html │ │ │ ├── note.html │ │ │ ├── notification.html │ │ │ ├── purge.html │ │ │ ├── put.html │ │ │ ├── results.html │ │ │ ├── silence.html │ │ │ ├── tokenList.html │ │ │ └── tokenNew.html │ │ └── templates │ │ │ └── index.html │ │ └── web.go ├── scollector │ ├── README.md │ ├── collectors │ │ ├── activedirectory_windows.go │ │ ├── apache_mod_info_linux.go │ │ ├── apache_mod_info_linux_test.go │ │ ├── aws.go │ │ ├── awsBilling.go │ │ ├── azureeabilling.go │ │ ├── cadvisor.go │ │ ├── cassandra_unix.go │ │ ├── chef_linux.go │ │ ├── collectors.go │ │ ├── collectors_test.go │ │ ├── conntrack_linux.go │ │ ├── cpu_windows.go │ │ ├── dell_hw.go │ │ ├── dfstat_darwin.go │ │ ├── disk.go │ │ ├── disk_linux.go │ │ ├── disk_linux_test.go │ │ ├── disk_test.go │ │ ├── disk_windows.go │ │ ├── dns_windows.go │ │ ├── dotnet_windows.go │ │ ├── dsc_windows.go │ │ ├── elasticsearch.go │ │ ├── exim_linux.go │ │ ├── extrahop.go │ │ ├── fake.go │ │ ├── fastly.go │ │ ├── github.go │ │ ├── google_analytics.go │ │ ├── google_webmaster.go │ │ ├── haproxy_unix.go │ │ ├── hbase_unix.go │ │ ├── hi.go │ │ ├── hp_eva_windows.go │ │ ├── httpunit.go │ │ ├── icmp.go │ │ ├── ifstat_linux.go │ │ ├── iis_windows.go │ │ ├── interval.go │ │ ├── iostat_darwin.go │ │ ├── keepalived_linux.go │ │ ├── local_listener.go │ │ ├── mem_windows.go │ │ ├── memcached_unix.go │ │ ├── metadata_darwin.go │ │ ├── metadata_linux.go │ │ ├── metadata_linux_test.go │ │ ├── metadata_windows.go │ │ ├── netbackup.go │ │ ├── network_windows.go │ │ ├── nexpose.go │ │ ├── ntp_unix.go │ │ ├── opentsdb.go │ │ ├── oracle.go │ │ ├── processes_darwin.go │ │ ├── processes_linux.go │ │ ├── processes_windows.go │ │ ├── procstats_linux.go │ │ ├── program.go │ │ ├── program_linux.go │ │ ├── puppet.go │ │ ├── puppet_linux.go │ │ ├── puppet_windows.go │ │ ├── rabbitmq.go │ │ ├── rabbitmq_test.go │ │ ├── railgun_linux.go │ │ ├── ras_windows.go │ │ ├── redis_counters.go │ │ ├── redis_linux.go │ │ ├── riak.go │ │ ├── snmp.go │ │ ├── snmp_bridge.go │ │ ├── snmp_cisco.go │ │ ├── snmp_ciscobgp.go │ │ ├── snmp_fortinet.go │ │ ├── snmp_ifaces.go │ │ ├── snmp_ips.go │ │ ├── snmp_lag.go │ │ ├── snmp_sys.go │ │ ├── sntp_windows.go │ │ ├── sql_windows.go │ │ ├── sqlagent_windows.go │ │ ├── stream.go │ │ ├── system_windows.go │ │ ├── systemd_linux.go │ │ ├── tag_override.go │ │ ├── varnish_unix.go │ │ ├── vmstat_darwin.go │ │ ├── vsphere.go │ │ ├── wmi_windows.go │ │ └── yum_update_linux.go │ ├── conf │ │ ├── conf.go │ │ ├── conf_darwin.go │ │ ├── conf_linux.go │ │ └── conf_windows.go │ ├── debug │ ├── doc.go │ ├── log_unix.go │ ├── main.go │ └── service_windows.go ├── silence │ └── main.go ├── snmpTester │ ├── main.go │ ├── static.go │ └── static │ │ └── index.html └── tsdbrelay │ ├── denormalize │ ├── denormalization.go │ └── denormalization_test.go │ ├── doc.go │ ├── integrationTest │ └── main.go │ └── main.go ├── collect ├── collect.go ├── eventListener.go └── queue.go ├── docker ├── Dockerfile ├── data │ ├── bosun.toml │ ├── bosunrules.conf │ ├── scollector.toml │ ├── supervisord-opentsdb.conf │ └── supervisord.conf ├── docker-compose.yml ├── hbase-site.xml ├── opentsdb.Dockerfile ├── start_hbase.sh └── tsdb │ ├── create_tsdb_tables.sh │ ├── opentsdb.conf │ └── start_opentsdb.sh ├── docs ├── .gitignore ├── 404.html ├── CNAME ├── README.md ├── _config.yml ├── _jekyll.config.yml ├── _layouts │ ├── default.html │ ├── goimport.html │ └── page.html ├── admin.md ├── api.md ├── call.md ├── cmd │ ├── backfill │ │ └── index.html │ ├── bosun │ │ ├── cache │ │ │ └── index.html │ │ ├── conf │ │ │ ├── index.html │ │ │ └── parse │ │ │ │ └── index.html │ │ ├── database │ │ │ ├── index.html │ │ │ └── test │ │ │ │ └── index.html │ │ ├── expr │ │ │ ├── index.html │ │ │ └── parse │ │ │ │ └── index.html │ │ ├── index.html │ │ ├── sched │ │ │ └── index.html │ │ ├── search │ │ │ └── index.html │ │ └── web │ │ │ └── index.html │ ├── scollector │ │ ├── collectors │ │ │ └── index.html │ │ ├── conf │ │ │ └── index.html │ │ └── index.html │ ├── silence │ │ └── index.html │ ├── snmpTester │ │ └── index.html │ └── tsdbrelay │ │ ├── denormalize │ │ └── index.html │ │ ├── index.html │ │ └── integrationTest │ │ └── index.html ├── collect │ └── index.html ├── configuration.md ├── definitions.md ├── docker.sh ├── documentation.md ├── downloads.md ├── examples.md ├── expressions.md ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── graphite │ └── index.html ├── index.md ├── media.md ├── metadata │ └── index.html ├── models │ └── index.html ├── notifications.md ├── opentsdb │ └── index.html ├── public │ ├── agent.png │ ├── anom_route_notification.png │ ├── anom_traffic_vol_graphite.png │ ├── app-store.svg │ ├── arch.png │ ├── bosun-logo-mark.svg │ ├── cog.svg │ ├── cpu_idle_graph_graphite.png │ ├── createToken.jpg │ ├── createdToken.png │ ├── database.svg │ ├── disk_forecast_notification.png │ ├── favicon.ico │ ├── grid.svg │ ├── hour-glass.svg │ ├── hw_notification.png │ ├── inbox.svg │ ├── netbackup_notification.png │ ├── rule_editor.jpg │ ├── rule_editor.xcf │ ├── sound-mute.svg │ ├── ss_dashboard.png │ ├── ss_expr.png │ ├── ss_graph.png │ ├── ss_host.png │ ├── ss_rule_results.png │ ├── ss_rule_template.png │ ├── ss_rule_timeline.png │ ├── stackexchange-logo.png │ ├── t_ill.jpg │ ├── t_stepthrough_1.jpg │ ├── t_stepthrough_2.jpg │ ├── t_stepthrough_3.jpg │ ├── tcp_notification.png │ ├── timeline.jpg │ ├── tux-large-bw.png │ ├── vimeo-logo.png │ └── windows-store.svg ├── quickstart.md ├── resources.md ├── scollector │ ├── external-collectors.md │ ├── index.html │ ├── javascripts │ │ └── scale.fix.js │ ├── process-monitoring.md │ └── stylesheets │ │ └── styles.css ├── scripts │ └── bootstrap.min.js ├── slackInvite.html ├── slog │ └── index.html ├── snmp │ ├── asn1 │ │ └── index.html │ ├── index.html │ └── mib │ │ └── index.html ├── styles │ ├── bootstrap.min.css │ └── font-awesome.min.css ├── system_configuration.md ├── t.md ├── usage.md ├── util │ └── index.html ├── version │ └── index.html └── vsphere │ └── index.html ├── go.mod ├── go.sum ├── graphite ├── graphite.go └── graphite_test.go ├── host ├── host.go ├── host_test.go ├── manager.go ├── manager_test.go ├── name.go └── name_test.go ├── metadata ├── metadata.go └── shared.go ├── mk_rpm_fpmdir.bosun-silence.txt ├── mk_rpm_fpmdir.scollector.txt ├── mk_rpm_fpmdir.tsdbrelay.txt ├── models ├── alertKey.go ├── errors.go ├── incidents.go └── silence.go ├── name ├── basic.go ├── basic_test.go ├── length.go ├── length_test.go ├── name.go ├── regexp.go └── regexp_test.go ├── opentsdb ├── duration.go ├── name.go ├── name_test.go ├── tsdb.go └── tsdb_test.go ├── package-lock.json ├── package.json ├── plugins ├── mysqlconf.py └── utils.py ├── slog ├── README.md ├── slog.go ├── slog_unix.go └── slog_windows.go ├── snmp ├── asn1 │ ├── LICENSE │ ├── asn1.go │ ├── asn1_test.go │ ├── common.go │ ├── marshal.go │ └── marshal_test.go ├── get.go ├── get_test.go ├── mib │ ├── mib.go │ └── mib_test.go ├── snmp.go ├── snmp_test.go ├── walk.go └── walk_test.go └── util ├── Windows_WMI_Metadata.linq ├── command.go ├── json.go ├── match.go ├── match_test.go ├── proxy.go └── util.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: "Bug: " 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | # Expected behaviour 10 | 11 | 14 | 15 | # Current behaviour 16 | 17 | 20 | 21 | ## Steps to reproduce 22 | 23 | 26 | 27 | 1. step 1 28 | 2. step 2 29 | 3. you get it... 30 | 31 | ## Context 32 | 33 | 37 | 38 | 39 | ## Logs 40 | 41 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: StackOverflow 4 | url: https://stackoverflow.com/questions/tagged/bosun 5 | about: Please ask and answer questions there. 6 | - name: Contributing guidelines 7 | url: https://github.com/bosun-monitor/bosun/blob/master/CONTRIBUTING.md 8 | about: Please read the contributing guidelines for this project. 9 | - name: Code Of Conduct 10 | url: https://github.com/bosun-monitor/bosun/blob/master/CODE_OF_CONDUCT.md 11 | about: This space is protected by our code of conduct. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F9E8 Feature request" 3 | about: Suggest an idea for this project 4 | title: "Feature request: " 5 | labels: new feature 6 | assignees: "" 7 | --- 8 | 9 | # Short description 10 | 11 | 12 | # How this feature will help you/your organisation 13 | 14 | # Possible solution or implementation details 15 | 16 | 17 | # Example/links if any 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ master ] 7 | 8 | jobs: 9 | checks: 10 | name: Code style 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.21 17 | id: go 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | - name: Check suspicious constructs 21 | run: make vet 22 | - name: Check order of imports 23 | run: make goimports-check 24 | - name: Check if modules were tidied up 25 | run: make tidy-check 26 | 27 | generated: 28 | name: Generated code 29 | runs-on: ubuntu-22.04 30 | steps: 31 | - name: Set up Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: 1.21 35 | id: go 36 | - name: Checkout 37 | uses: actions/checkout@v2 38 | - name: Install dependencies 39 | run: make deps 40 | - name: Check whether go generate has run 41 | run: export PATH=$(npm bin):$PATH && make generate 42 | 43 | build_and_test: 44 | name: Build and test 45 | runs-on: ubuntu-22.04 46 | steps: 47 | - name: Set up Go 48 | uses: actions/setup-go@v2 49 | with: 50 | go-version: 1.21 51 | id: go 52 | - name: Checkout 53 | uses: actions/checkout@v2 54 | - name: Compile 55 | run: make build 56 | - name: Tests 57 | run: make test 58 | - name: Build bosun binary 59 | run: make bosun 60 | - name: Test startup 61 | run: build/test-startup.sh 62 | 63 | cross-compile: 64 | runs-on: ubuntu-22.04 65 | strategy: 66 | matrix: 67 | os: [ "linux", "darwin", "windows" ] 68 | name: Build ${{ matrix.os }} binary 69 | steps: 70 | - name: Checkout 71 | uses: actions/checkout@v2 72 | - name: Setup Go 73 | uses: actions/setup-go@v2 74 | with: 75 | go-version: 1.21 76 | - name: Build ${{ matrix.os }} binary 77 | run: make build-${{ matrix.os }} 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmd/bosun/dev.conf 3 | docs/.jekyll-metadata 4 | node_modules/ 5 | cmd/bosun/ledis_data/ 6 | cmd/bosun/bosun 7 | cmd/scollector/scollector.toml 8 | cmd/scollector/scollector.exe 9 | cmd/bosun/bosun.toml 10 | cmd/bosun/bosunrules.conf 11 | cmd/scollector/scollector 12 | buildoutput 13 | test-report.out 14 | coverage.out 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.21.x 5 | 6 | env: 7 | - GO111MODULE=on 8 | 9 | cache: 10 | directories: 11 | - $GOPATH/pkg/mod 12 | 13 | notifications: 14 | email: false 15 | 16 | install: 17 | - chmod +x ${TRAVIS_BUILD_DIR}/build/validate.sh 18 | - npm i -g typescript@2.4.2 19 | 20 | script: ${TRAVIS_BUILD_DIR}/build/validate.sh 21 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Original Creators: 2 | Kyle Brandt 3 | Matt Jibson 4 | 5 | Significant Contributors: 6 | Greg Bray 7 | Dieter Plaetinck 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Stack Exchange 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /_version/version.go: -------------------------------------------------------------------------------- 1 | // Package version holds some version data common to bosun and scollector. 2 | // Most of these values will be inserted at build time with `-ldFlags` directives for official builds. 3 | package version // import "bosun.org/_version" 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "time" 9 | 10 | "github.com/kardianos/osext" 11 | ) 12 | 13 | // These variables will be set at linking time for official builds. 14 | // build.go will set date and sha, but `go get` will set none of these. 15 | var ( 16 | // Version number for official releases Updated manually before each release. 17 | Version = "0.9.0-preview" 18 | 19 | // Set to any non-empty value by official release script 20 | OfficialBuild string 21 | // Date and time of build. Should be in YYYYMMDDHHMMSS format 22 | VersionDate string 23 | // VersionSHA should be set at build time as the most recent commit hash. 24 | VersionSHA string 25 | ) 26 | 27 | // Get a string representing the version information for the current binary. 28 | func GetVersionInfo(app string) string { 29 | var sha, build string 30 | version := ShortVersion() 31 | if buildTime, err := time.Parse("20060102150405", VersionDate); err == nil { 32 | build = " built " + buildTime.Format(time.RFC3339) 33 | } else { 34 | currentFilePath, err := osext.Executable() 35 | if err == nil { 36 | info, err := os.Stat(currentFilePath) 37 | if err == nil { 38 | build = " last modified " + info.ModTime().Format(time.RFC3339) 39 | } 40 | } 41 | } 42 | if VersionSHA != "" { 43 | sha = fmt.Sprintf(" (%s)", VersionSHA) 44 | } 45 | return fmt.Sprintf("%s version %s%s%s", app, version, sha, build) 46 | } 47 | 48 | func ShortVersion() string { 49 | version := Version 50 | 51 | if OfficialBuild == "" { 52 | version += "-dev" 53 | } 54 | 55 | return version 56 | } 57 | -------------------------------------------------------------------------------- /annotate/backend/backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "bosun.org/annotate" 8 | ) 9 | 10 | type Backend interface { 11 | InsertAnnotation(a *annotate.Annotation) error 12 | GetAnnotation(id string) (*annotate.Annotation, bool, error) 13 | GetAnnotations(start, end *time.Time, filters ...FieldFilter) (annotate.Annotations, error) 14 | DeleteAnnotation(id string) error 15 | GetFieldValues(field string) ([]string, error) 16 | InitBackend() error 17 | } 18 | 19 | const docType = "annotation" 20 | 21 | var unInitErr = fmt.Errorf("backend has not been initialized") 22 | 23 | type FieldFilter struct { 24 | Field string 25 | Verb string 26 | Not bool 27 | Value string 28 | } 29 | 30 | const Is = "Is" 31 | const Empty = "Empty" 32 | -------------------------------------------------------------------------------- /annotate/cmd/annotate/os_unix.go: -------------------------------------------------------------------------------- 1 | //+build linux darwin 2 | 3 | package main 4 | 5 | const timeFormat = "Mon Jan 2 15:04:05 MST 2006" 6 | const timeFormatUsage = "default returned by the date command" 7 | const USER_ENV = "USER" 8 | -------------------------------------------------------------------------------- /annotate/cmd/annotate/os_windows.go: -------------------------------------------------------------------------------- 1 | //+build windows 2 | 3 | package main 4 | 5 | const timeFormat = "Monday, January 2, 2006 10:04:05 PM" 6 | const timeFormatUsage = "default returned by the powershell Get-Date commandlet. Assumed to be UTC" 7 | const USER_ENV = "USERNAME" 8 | -------------------------------------------------------------------------------- /annotate/mk_rpm_fpmdir.annotate_cli.txt: -------------------------------------------------------------------------------- 1 | #Perm Destination Source 2 | exec /usr/bin/annotate cmd/annotate/annotate 3 | -------------------------------------------------------------------------------- /annotate/mk_rpm_fpmdir.annotate_server.txt: -------------------------------------------------------------------------------- 1 | #Perm Destination Source 2 | exec /usr/bin/annotate_server cmd/server/server 3 | -------------------------------------------------------------------------------- /annotate/web/config.toml: -------------------------------------------------------------------------------- 1 | ListenAddress = ":8070" 2 | 3 | [[ElasticClusters]] 4 | Servers = ["http://ny-lselastic01:9200"] 5 | Index = "annotate" 6 | -------------------------------------------------------------------------------- /annotate/web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/annotate/web/static/favicon.ico -------------------------------------------------------------------------------- /annotate/web/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /annotate/web/static/js/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ambientDependencies": { 3 | "angular": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular.d.ts#17ef40452039d19e06dc2a3815ea898c505860fa", 4 | "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#fab0b336b0414fac23963bde83f7d7077f6cf14c", 5 | "underscore": "github:DefinitelyTyped/DefinitelyTyped/underscore/underscore.d.ts#669e1b73f18ea6ea15bfbfbbdad6501bfa0ff340" 6 | }, 7 | "dependencies": { 8 | "moment": "github:typed-typings/npm-moment#a4075cd50e63efbedd850f654594f293ab81a385" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /annotate/web/static/js/typings/browser.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /annotate/web/static/js/typings/main.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /annotate/web/static/partials/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
IdStartDateEndDateSourceHostCreationUserOwnerCategoryUrlMessage
40 | Edit 41 | Delete 42 | {{url(a.Url)}}
55 |
56 |
-------------------------------------------------------------------------------- /build.scollector.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run build/build.go -scollector -output cmd/scollector 4 | -------------------------------------------------------------------------------- /build.tsdbrelay.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run build/build.go -tsdbrelay -output cmd/tsdbrelay 4 | -------------------------------------------------------------------------------- /build/build.go: -------------------------------------------------------------------------------- 1 | // Simple script to build bosun and scollector. This is not required, but it will properly insert version date and commit 2 | // metadata into the resulting binaries, which `go build` will not do by default. 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | var ( 17 | shaFlag = flag.String("sha", "", "SHA to embed.") 18 | buildBosun = flag.Bool("bosun", false, "Only build Bosun.") 19 | buildTsdb = flag.Bool("tsdbrelay", false, "Only build tsdbrelay") 20 | buildScollector = flag.Bool("scollector", false, "Only build scollector.") 21 | output = flag.String("output", "", "Output directory; defaults to $GOPATH/bin.") 22 | 23 | allProgs = []string{"bosun", "scollector", "tsdbrelay"} 24 | ) 25 | 26 | func main() { 27 | flag.Parse() 28 | // Get current commit SHA 29 | sha := *shaFlag 30 | if sha == "" { 31 | cmd := exec.Command("git", "rev-parse", "HEAD") 32 | cmd.Stderr = os.Stderr 33 | output, err := cmd.Output() 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | sha = strings.TrimSpace(string(output)) 38 | } 39 | 40 | timeStr := time.Now().UTC().Format("20060102150405") 41 | ldFlags := fmt.Sprintf("-X bosun.org/_version.VersionSHA=%s -X bosun.org/_version.VersionDate=%s", sha, timeStr) 42 | 43 | progs := allProgs 44 | if *buildBosun { 45 | progs = []string{"bosun"} 46 | } else if *buildScollector { 47 | progs = []string{"scollector"} 48 | } else if *buildTsdb { 49 | progs = []string{"tsdbrelay"} 50 | } 51 | for _, app := range progs { 52 | fmt.Println("building", app) 53 | var args []string 54 | if *output != "" { 55 | args = append(args, "build", "-o", filepath.Join(*output, app)) 56 | } else { 57 | args = append(args, "install") 58 | } 59 | args = append(args, "-ldflags", ldFlags, fmt.Sprintf("bosun.org/cmd/%s", app)) 60 | fmt.Println("go", strings.Join(args, " ")) 61 | cmd := exec.Command("go", args...) 62 | cmd.Stdout = os.Stdout 63 | cmd.Stderr = os.Stderr 64 | err := cmd.Run() 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /build/dockerRelease.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Must be run from $GOPATH/src/bosun.org, not the build directory. 3 | set -e 4 | 5 | rm -rf buildoutput 6 | mkdir buildoutput 7 | 8 | docker run --rm \ 9 | -v "$PWD":/src/bosun.org \ 10 | -v "$PWD"/buildoutput:/output \ 11 | -w /src/bosun.org \ 12 | -e OUTPUTDIR=/output/ \ 13 | -e GITHUB_ACCESS_TOKEN=$GITHUB_ACCESS_TOKEN \ 14 | golang:1.21 /src/bosun.org/build/release.sh /src/bosun.org 15 | -------------------------------------------------------------------------------- /build/generate/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bosun.org/cmd/bosun/web" 5 | ) 6 | 7 | func main() { 8 | web.RunTsc() 9 | web.RunEsc() 10 | } 11 | -------------------------------------------------------------------------------- /build/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -z "$1" ]; then 5 | echo "Usage: `basename $0` /path/to/bosun/repository" 6 | exit 1 7 | fi 8 | 9 | BOSUN_PATH=$1 10 | 11 | TIME=`date +%Y%m%d%H%M%S` 12 | export GIT_SHA=`cd ${BOSUN_PATH}; git rev-parse HEAD` 13 | 14 | build() 15 | { 16 | export GOOS=$1 17 | export GOARCH=$2 18 | EXT="" 19 | if [ $GOOS = "windows" ]; then 20 | EXT=".exe" 21 | fi 22 | if [ $GOARCH = "arm" ]; then 23 | export GOARM=${3-6} 24 | EXT="v${GOARM}" 25 | fi 26 | echo $GOOS $GOARCH $EXT 27 | if $BOSUN; then 28 | go build -o ${OUTPUTDIR}bosun-$GOOS-$GOARCH$EXT -ldflags "-X bosun.org/_version.VersionSHA=$GIT_SHA -X bosun.org/_version.OfficialBuild=true -X bosun.org/_version.VersionDate=$TIME" bosun.org/cmd/bosun 29 | go build -o ${OUTPUTDIR}tsdbrelay-$GOOS-$GOARCH$EXT -ldflags "-X bosun.org/_version.VersionSHA=$GIT_SHA -X bosun.org/_version.OfficialBuild=true -X bosun.org/_version.VersionDate=$TIME" bosun.org/cmd/tsdbrelay 30 | fi 31 | go build -o ${OUTPUTDIR}scollector-$GOOS-$GOARCH$EXT -ldflags "-X bosun.org/_version.VersionSHA=$GIT_SHA -X bosun.org/_version.OfficialBuild=true -X bosun.org/_version.VersionDate=$TIME" bosun.org/cmd/scollector 32 | } 33 | 34 | BOSUN=true 35 | for GOOS in windows linux; do 36 | for GOARCH in amd64 386; do 37 | build $GOOS $GOARCH 38 | done 39 | done 40 | # darwin/386 is no longer supported 41 | build darwin amd64 42 | 43 | BOSUN=false 44 | 45 | build linux arm 5 46 | build linux arm 6 47 | build linux arm 7 48 | 49 | if [ "$GITHUB_ACCESS_TOKEN" = "" ]; then 50 | echo GITHUB_ACCESS_TOKEN not set: not running githubRelease.go 51 | else 52 | GOOS=linux 53 | GOARCH=amd64 54 | export BUILD_NUMBER=`${OUTPUTDIR}bosun-linux-amd64 -version | awk '{print $3}'` 55 | go run build/release/githubRelease.go 56 | fi 57 | -------------------------------------------------------------------------------- /build/syncTokens/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | ) 9 | 10 | var ( 11 | src = flag.String("src", "", "Source redis server") 12 | dst = flag.String("dst", "", "Destination redis server") 13 | 14 | key = flag.String("key", "accessTokens", "Redis Key to sync") 15 | ) 16 | 17 | func main() { 18 | flag.Parse() 19 | if *src == "" || *dst == "" { 20 | log.Fatal("Both src and dst redis servers required") 21 | } 22 | 23 | srcC, err := redis.Dial("tcp", *src) 24 | if err != nil { 25 | log.Fatal("Dialing src: ", err) 26 | } 27 | defer srcC.Close() 28 | dstC, err := redis.Dial("tcp", *dst) 29 | if err != nil { 30 | log.Fatal("Dialing dst: ", err) 31 | } 32 | defer dstC.Close() 33 | 34 | ser, err := redis.String(srcC.Do("DUMP", *key)) 35 | if err != nil { 36 | log.Fatal("Dumping key: ", err) 37 | } 38 | 39 | _, err = dstC.Do("DEL", *key) 40 | if err != nil { 41 | log.Fatal("Deleting at destination: ", err) 42 | } 43 | 44 | _, err = dstC.Do("RESTORE", *key, 0, ser) 45 | if err != nil { 46 | log.Fatal("Restoring: ", err) 47 | } 48 | 49 | log.Println("Complete") 50 | } 51 | -------------------------------------------------------------------------------- /build/test-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Testing that bosun starts and stops cleanly" 4 | 5 | # Generate a minimal config 6 | echo 'RuleFilePath = "/tmp/rule.conf"' > /tmp/bosun.toml 7 | touch /tmp/rule.conf 8 | 9 | # Wait for at most 30 seconds before considering the launch a failure 10 | timeout 30 ./bosun -c /tmp/bosun.toml & TIMEOUT_PID=$! 11 | BOSUN_START_RESULT=$? 12 | 13 | # Give Bosun 5 seconds to start, then stop cleanly 14 | sleep 5 15 | kill -SIGINT $TIMEOUT_PID 16 | BOSUN_SIGNAL_RESULT=$? 17 | 18 | # Wait for the process to exit 19 | wait $TIMEOUT_PID 20 | TIMEOUT_RESULT=$? 21 | 22 | if [ "$BOSUN_START_RESULT" != 0 ]; then 23 | echo "Failed to start bosun cleanly. Exit code ${BOSUN_START_RESULT}" 24 | fi 25 | if [ "$BOSUN_SIGNAL_RESULT" != 0 ]; then 26 | echo "Failed to signal bosun to stop cleanly. Likely crashed before signal sent." 27 | fi 28 | if [ "$BOSUN_STOP_RESULT" != 0 ]; then 29 | echo "Failed to stop bosun cleanly. Exit code ${TIMEOUT_RESULT} (124=60s test timeout reached)" 30 | fi 31 | 32 | (( RESULT = BOSUN_START_RESULT | BOSUN_SIGNAL_RESULT | BOSUN_STOP_RESULT )) 33 | exit $RESULT 34 | -------------------------------------------------------------------------------- /cmd/bosun/README.md: -------------------------------------------------------------------------------- 1 | # bosun 2 | 3 | Time Series Alerting Framework 4 | 5 | # documentation 6 | 7 | [http://bosun.org](http://bosun.org) 8 | 9 | # usage 10 | 11 | `bosun [-c=bosun.toml] [-t]` 12 | 13 | `-c` specifies the config file to use, defaults to `bosun.toml`. `-t` parses the config file, validates it, and exits. 14 | 15 | You can use the included bosun.example.toml as a basis for your bosun.toml. 16 | 17 | # installation/binaries 18 | 19 | [http://bosun.org/#installation](http://bosun.org/#installation) 20 | 21 | # docker 22 | 23 | [https://registry.hub.docker.com/u/stackexchange/bosun/](https://registry.hub.docker.com/u/stackexchange/bosun/) 24 | -------------------------------------------------------------------------------- /cmd/bosun/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache // import "bosun.org/cmd/bosun/cache" 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/golang/groupcache/lru" 7 | "github.com/golang/groupcache/singleflight" 8 | ) 9 | 10 | type Cache struct { 11 | g singleflight.Group 12 | 13 | sync.Mutex 14 | lru *lru.Cache 15 | Name string 16 | } 17 | 18 | // New creates a new LRU cache of the request length with 19 | // an exported Name for instrumentation 20 | func New(name string, MaxEntries int) *Cache { 21 | return &Cache{ 22 | lru: lru.New(MaxEntries), 23 | Name: name, 24 | } 25 | } 26 | 27 | // Get returns a cached value based on the passed key or runs the passed function to get the value 28 | // if there is no corresponding value in the cache 29 | func (c *Cache) Get(key string, getFn func() (interface{}, error)) (i interface{}, err error, hit bool) { 30 | if c == nil { 31 | i, err = getFn() 32 | return 33 | } 34 | c.Lock() 35 | result, ok := c.lru.Get(key) 36 | c.Unlock() 37 | if ok { 38 | return result, nil, true 39 | } 40 | // our lock only serves to protect the lru. 41 | // we can (and should!) do singleflight requests concurrently 42 | i, err = c.g.Do(key, func() (interface{}, error) { 43 | v, err := getFn() 44 | if err == nil { 45 | c.Lock() 46 | c.lru.Add(key, v) 47 | c.Unlock() 48 | } 49 | return v, err 50 | }) 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /cmd/bosun/conf/bytesize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package conf 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type ByteSize float64 12 | 13 | const ( 14 | _ = iota 15 | KB ByteSize = 1 << (10 * iota) 16 | MB 17 | GB 18 | TB 19 | PB 20 | EB 21 | ZB 22 | YB 23 | ) 24 | 25 | func (b ByteSize) String() string { 26 | switch { 27 | case b >= YB: 28 | return fmt.Sprintf("%.2fYB", b/YB) 29 | case b >= ZB: 30 | return fmt.Sprintf("%.2fZB", b/ZB) 31 | case b >= EB: 32 | return fmt.Sprintf("%.2fEB", b/EB) 33 | case b >= PB: 34 | return fmt.Sprintf("%.2fPB", b/PB) 35 | case b >= TB: 36 | return fmt.Sprintf("%.2fTB", b/TB) 37 | case b >= GB: 38 | return fmt.Sprintf("%.2fGB", b/GB) 39 | case b >= MB: 40 | return fmt.Sprintf("%.2fMB", b/MB) 41 | case b >= KB: 42 | return fmt.Sprintf("%.2fKB", b/KB) 43 | } 44 | return fmt.Sprintf("%.2fB", b) 45 | } 46 | -------------------------------------------------------------------------------- /cmd/bosun/conf/conf_test.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | func TestSquelch(t *testing.T) { 11 | s := Squelches{ 12 | map[string]*regexp.Regexp{ 13 | "x": regexp.MustCompile("ab"), 14 | "y": regexp.MustCompile("bc"), 15 | }, 16 | map[string]*regexp.Regexp{ 17 | "x": regexp.MustCompile("ab"), 18 | "z": regexp.MustCompile("de"), 19 | }, 20 | } 21 | type squelchTest struct { 22 | tags opentsdb.TagSet 23 | expect bool 24 | } 25 | tests := []squelchTest{ 26 | { 27 | opentsdb.TagSet{ 28 | "x": "ab", 29 | }, 30 | false, 31 | }, 32 | { 33 | opentsdb.TagSet{ 34 | "x": "abe", 35 | "y": "obcx", 36 | }, 37 | true, 38 | }, 39 | { 40 | opentsdb.TagSet{ 41 | "x": "abe", 42 | "z": "obcx", 43 | }, 44 | false, 45 | }, 46 | { 47 | opentsdb.TagSet{ 48 | "x": "abe", 49 | "z": "ouder", 50 | }, 51 | true, 52 | }, 53 | { 54 | opentsdb.TagSet{ 55 | "x": "ae", 56 | "y": "bc", 57 | "z": "de", 58 | }, 59 | false, 60 | }, 61 | } 62 | for _, test := range tests { 63 | got := s.Squelched(test.tags) 64 | if got != test.expect { 65 | t.Errorf("for %v got %v, expected %v", test.tags, got, test.expect) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cmd/bosun/conf/lookup.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "bosun.org/cmd/bosun/search" 5 | "bosun.org/models" 6 | "bosun.org/opentsdb" 7 | ) 8 | 9 | // TODO: remove this and merge it with Lookup 10 | type ExprLookup struct { 11 | Tags []string 12 | Entries []*ExprEntry 13 | } 14 | 15 | type ExprEntry struct { 16 | AlertKey models.AlertKey 17 | Values map[string]string 18 | } 19 | 20 | func (lookup *ExprLookup) Get(key string, tag opentsdb.TagSet) (value string, ok bool) { 21 | for _, entry := range lookup.Entries { 22 | value, ok = entry.Values[key] 23 | if !ok { 24 | continue 25 | } 26 | match := true 27 | for ak, av := range entry.AlertKey.Group() { 28 | matches, err := search.Match(av, []string{tag[ak]}) 29 | if err != nil { 30 | return "", false 31 | } 32 | if len(matches) == 0 { 33 | match = false 34 | break 35 | } 36 | } 37 | if !match { 38 | continue 39 | } 40 | return 41 | } 42 | return "", false 43 | } 44 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/crit-notification-no-template: -------------------------------------------------------------------------------- 1 | notification n { 2 | print = true 3 | } 4 | 5 | alert a { 6 | crit = 1 7 | critNotification = n 8 | } 9 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/crit-warn-unmatching-tags: -------------------------------------------------------------------------------- 1 | alert broken { 2 | crit = avg(q("avg:o{a=b,c=d}", "", "")) 3 | warn = avg(q("avg:o{c=d}", "", "")) 4 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/depends-no-overlap: -------------------------------------------------------------------------------- 1 | alert broken { 2 | depends = avg(q("avg:o{x=b,y=d}", "", "")) 3 | crit = avg(q("avg:o{a=b,c=d}", "", "")) 4 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/log-no-notification: -------------------------------------------------------------------------------- 1 | alert a { 2 | crit = 1 3 | log = true 4 | } 5 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/lookup-key-pairs: -------------------------------------------------------------------------------- 1 | lookup l { 2 | entry a=1,b=2 { } 3 | entry a=3 { } 4 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/lookup-key-pairs-dup: -------------------------------------------------------------------------------- 1 | lookup l { 2 | entry a=1,b=2 { } 3 | entry b=2,a=1 { } 4 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/notification-lookup-bad-notification: -------------------------------------------------------------------------------- 1 | lookup l { 2 | entry a=* { 3 | v = n 4 | } 5 | } 6 | 7 | alert a { 8 | critNotification = lookup("l", "v") 9 | } 10 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/notification-lookup-table: -------------------------------------------------------------------------------- 1 | alert nc 2 | critNotification = lookup("l", "v") 3 | } 4 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/invalid/number-func-args: -------------------------------------------------------------------------------- 1 | alert broken { 2 | warn = q("avg:o", "") > 0 3 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/parse_test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | func isValid(fname string, t *testing.T) (bool, error) { 11 | b, err := ioutil.ReadFile(fname) 12 | if err != nil { 13 | return false, nil 14 | } 15 | _, err = Parse(fname, string(b)) 16 | return err == nil, err 17 | } 18 | 19 | func testDir(dirname string, valid bool, t *testing.T) { 20 | files, _ := ioutil.ReadDir(dirname) 21 | for _, f := range files { 22 | p := filepath.Join(dirname, f.Name()) 23 | if got, err := isValid(p, t); valid != got { 24 | t.Fatalf("%v: expected %v: %v", p, valid, err) 25 | } 26 | } 27 | } 28 | 29 | func TestLex(t *testing.T) { 30 | testDir("test_valid", true, t) 31 | testDir("test_invalid", false, t) 32 | } 33 | 34 | func _TestPrint(t *testing.T) { 35 | fname := "test_valid/4" 36 | b, err := ioutil.ReadFile(fname) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | c, err := Parse(fname, string(b)) 41 | if err != nil { 42 | t.Error(err) 43 | } else { 44 | fmt.Print(c.Root) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_invalid/1: -------------------------------------------------------------------------------- 1 | [aoensth 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_invalid/2: -------------------------------------------------------------------------------- 1 | aoenst = `aoenth 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_invalid/3: -------------------------------------------------------------------------------- 1 | aoesnuth 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_invalid/4: -------------------------------------------------------------------------------- 1 | aoenth aoenth = aoent 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_valid/1: -------------------------------------------------------------------------------- 1 | aoneth = 23oerho90e o0e9hoant 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_valid/2: -------------------------------------------------------------------------------- 1 | section name {} 2 | -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_valid/3: -------------------------------------------------------------------------------- 1 | section 1 { 2 | 2 = 3 3 | } 4 | 5 | # comment aosent 6 | section 2 { 7 | 5=2309,. h,.90 8 | 6=`oaenuhoae09uho 9 | oeu09ho090ho 10 | 11 | 12 | 23` 13 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/rule/parse/test_valid/4: -------------------------------------------------------------------------------- 1 | 1 = 2 2 | 3 = 4 3 | 5 = 6 4 | 7 = 8 5 | 6 | section 1 { 7 | 2 = 3 8 | $4 = eoth $v 9 | oe = eee $4 ou 10 | } 11 | 12 | # comment aosent 13 | section 4 { 14 | 5=2309,. h,.90 15 | 6=`oaenuhoae09uho 16 | oeu09ho090ho 17 | 18 | 19 | 23` 20 | bleh other.sonteh=,|* { 21 | sohe = 0houe0oa euo 22 | $oaunho = ut 23 | } 24 | } -------------------------------------------------------------------------------- /cmd/bosun/conf/system_elastic_all.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "fmt" 5 | 6 | "bosun.org/cmd/bosun/expr" 7 | "bosun.org/slog" 8 | ) 9 | 10 | // ParseESConfig return expr.ElasticHost 11 | func parseESConfig(sc *SystemConf) expr.ElasticHosts { 12 | store := make(map[string]expr.ElasticConfig) 13 | for hostPrefix, value := range sc.ElasticConf { 14 | // build es config per cluster 15 | var cfg expr.ElasticConfig 16 | switch expr.ESVersion(value.Version) { 17 | case expr.ESV2: 18 | cfg = parseESConfig2(value) 19 | case expr.ESV5: 20 | cfg = parseESConfig5(value) 21 | case expr.ESV6: 22 | cfg = parseESConfig6(value) 23 | case expr.ESV7: 24 | cfg = parseESConfig7(value) 25 | case "": 26 | slog.Fatal(fmt.Errorf(`conf: [ElasticConf.%s]: Version is required a field (supported values for Version are: "v2", "v5", "v6" and "v7")`, hostPrefix)) 27 | default: 28 | slog.Fatal(fmt.Errorf(`conf: [ElasticConf.%s]: invalid elastic version: %s (supported versions are: "v2", "v5", "v6" and "v7")`, hostPrefix, value.Version)) 29 | } 30 | 31 | cfg.Version = expr.ESVersion(value.Version) 32 | store[hostPrefix] = cfg 33 | } 34 | 35 | return expr.ElasticHosts{Hosts: store} 36 | } 37 | 38 | // ParseESConfig return expr.ElasticHost 39 | func parseESAnnoteConfig(sc *SystemConf) expr.ElasticConfig { 40 | var cfg expr.ElasticConfig 41 | if len(sc.AnnotateConf.Hosts) == 0 { 42 | return cfg 43 | } 44 | switch expr.ESVersion(sc.AnnotateConf.Version) { 45 | case expr.ESV2: 46 | cfg = parseESConfig2(ElasticConf(sc.AnnotateConf)) 47 | case expr.ESV5: 48 | cfg = parseESConfig5(ElasticConf(sc.AnnotateConf)) 49 | case expr.ESV6: 50 | cfg = parseESConfig6(ElasticConf(sc.AnnotateConf)) 51 | case expr.ESV7: 52 | cfg = parseESConfig7(ElasticConf(sc.AnnotateConf)) 53 | case "": 54 | slog.Fatal(fmt.Errorf(`conf: [AnnotateConf]: Version is required a field (supported values for Version are: "v2", "v5", "v6" and "v7")`)) 55 | default: 56 | slog.Fatal(fmt.Errorf(`conf: [AnnotateConf]: invalid elastic version: %s (supported versions are: "v2", "v5", "v6" and "v7")`, sc.AnnotateConf.Version)) 57 | } 58 | 59 | cfg.Version = expr.ESVersion(sc.AnnotateConf.Version) 60 | return cfg 61 | } 62 | -------------------------------------------------------------------------------- /cmd/bosun/conf/test.toml: -------------------------------------------------------------------------------- 1 | ../bosun.example.toml -------------------------------------------------------------------------------- /cmd/bosun/database/metric_metadata.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | ) 9 | 10 | /* 11 | Metric metadata is the fields associated with every metric: desc, rate and unit. 12 | They are stored as a simple hash structure: 13 | 14 | mmeta:{{metric}} -> {desc:"",unit:"",rate:"",lastTouched:123} 15 | 16 | lastTouched time is unix timestamp of last time this metric metadata was set. 17 | 18 | */ 19 | 20 | func metricMetaKey(metric string) string { 21 | return fmt.Sprintf("mmeta:%s", metric) 22 | } 23 | 24 | const metricMetaTTL = int((time.Hour * 24 * 7) / time.Second) 25 | 26 | func (d *dataAccess) Metadata() MetadataDataAccess { 27 | return d 28 | } 29 | 30 | func (d *dataAccess) PutMetricMetadata(metric string, field string, value string) error { 31 | if field != "desc" && field != "unit" && field != "rate" { 32 | return fmt.Errorf("Unknown metric metadata field: %s", field) 33 | } 34 | conn := d.Get() 35 | defer conn.Close() 36 | _, err := conn.Do("HMSET", metricMetaKey(metric), field, value, "lastTouched", time.Now().UTC().Unix()) 37 | return err 38 | } 39 | 40 | func (d *dataAccess) GetMetricMetadata(metric string) (*MetricMetadata, error) { 41 | conn := d.Get() 42 | defer conn.Close() 43 | v, err := redis.Values(conn.Do("HGETALL", metricMetaKey(metric))) 44 | if err != nil { 45 | return nil, err 46 | } 47 | if len(v) == 0 { 48 | return nil, nil 49 | } 50 | mm := &MetricMetadata{} 51 | if err := redis.ScanStruct(v, mm); err != nil { 52 | return nil, err 53 | } 54 | return mm, nil 55 | } 56 | -------------------------------------------------------------------------------- /cmd/bosun/database/models.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "bosun.org/opentsdb" 5 | ) 6 | 7 | type MetricMetadata struct { 8 | Desc string `redis:"desc" json:",omitempty"` 9 | Unit string `redis:"unit" json:",omitempty"` 10 | Rate string `redis:"rate" json:",omitempty"` 11 | LastTouched int64 `redis:"lastTouched"` 12 | } 13 | 14 | type TagMetadata struct { 15 | Tags opentsdb.TagSet 16 | Name string 17 | Value string 18 | LastTouched int64 19 | } 20 | 21 | type LastInfo struct { 22 | LastVal float64 23 | DiffFromPrev float64 24 | Timestamp int64 25 | } 26 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/config_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "testing" 5 | 6 | "bosun.org/host" 7 | "bosun.org/util" 8 | ) 9 | 10 | func TestConfigSave(t *testing.T) { 11 | hm, err := host.NewManager(false) 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | util.SetHostManager(hm) 16 | 17 | cd := testData.Configs() 18 | 19 | hash, err := cd.SaveTempConfig("test123") 20 | check(t, err) 21 | 22 | recoverd, err := cd.GetTempConfig(hash) 23 | check(t, err) 24 | if recoverd != "test123" { 25 | t.Fatalf("Loaded config doesn't match: %s", recoverd) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/database_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "testing" 10 | "time" 11 | 12 | "bosun.org/cmd/bosun/database" 13 | ) 14 | 15 | // data access object to use for all unit tests. Pointed at ephemeral ledis, or redis server passed in with --redis=addr 16 | var testData database.DataAccess 17 | 18 | func TestMain(m *testing.M) { 19 | rand.Seed(time.Now().UnixNano()) 20 | var closeF func() 21 | testData, closeF = StartTestRedis(9993) 22 | status := m.Run() 23 | closeF() 24 | os.Exit(status) 25 | } 26 | 27 | var cleanups = []func(){} 28 | 29 | // use random keys in tests to avoid conflicting test data. 30 | func randString(l int) string { 31 | s := "" 32 | for len(s) < l { 33 | s += string("abcdefghijklmnopqrstuvwxyz"[rand.Intn(26)]) 34 | } 35 | return s 36 | } 37 | 38 | func check(t *testing.T, err error) { 39 | if err != nil { 40 | s := err.Error() 41 | if _, filename, line, ok := runtime.Caller(1); ok { 42 | s = fmt.Sprintf("%s:%d: %v", filepath.Base(filename), line, s) 43 | } 44 | t.Fatal(s) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/metric_metadata_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMetricMetadata_RoundTrip(t *testing.T) { 8 | metric := randString(5) 9 | if err := testData.Metadata().PutMetricMetadata(metric, "desc", "cpu of a server"); err != nil { 10 | t.Fatal(err) 11 | } 12 | if err := testData.Metadata().PutMetricMetadata(metric, "unit", "pct"); err != nil { 13 | t.Fatal(err) 14 | } 15 | meta, err := testData.Metadata().GetMetricMetadata(metric) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | if meta == nil { 20 | t.Fatal("did not find metadata I put in.") 21 | } 22 | if meta.Desc != "cpu of a server" { 23 | t.Fatal("Wrong description.") 24 | } 25 | if meta.Unit != "pct" { 26 | t.Fatal("Wrong Unit.") 27 | } 28 | } 29 | 30 | func TestMetricMetadata_NoneExists(t *testing.T) { 31 | meta, err := testData.Metadata().GetMetricMetadata("asfaklsfjlkasjf") 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | if meta != nil { 36 | t.Fatal("Should return nil if not exist") 37 | } 38 | } 39 | 40 | func TestMetricMetadata_BadField(t *testing.T) { 41 | if err := testData.Metadata().PutMetricMetadata(randString(7), "desc1", "foo"); err == nil { 42 | t.Fatal("Expected failure to set bad metric metadata field") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/notifications_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "bosun.org/models" 8 | ) 9 | 10 | func TestNotifications_RoundTrip(t *testing.T) { 11 | 12 | nd := testData.Notifications() 13 | notTime := time.Now().UTC().Add(-10 * time.Hour).Truncate(time.Second) 14 | future := time.Now().UTC().Add(time.Hour).Truncate(time.Second) 15 | oneMin := time.Now().UTC().Add(time.Minute).Truncate(time.Second) 16 | 17 | // with nothing pending, next time should be an hour from now 18 | next, err := nd.GetNextNotificationTime() 19 | check(t, err) 20 | if next != future { 21 | t.Fatalf("wrong next time. %s != %s", next, future) 22 | } 23 | 24 | // add notifications 25 | err = nd.InsertNotification(models.AlertKey("notak{foo=a}"), "chat", notTime) 26 | check(t, err) 27 | err = nd.InsertNotification(models.AlertKey("notak{foo=b}"), "chat", oneMin) 28 | check(t, err) 29 | err = nd.InsertNotification(models.AlertKey("notak{foo=c}"), "chat", future) 30 | check(t, err) 31 | 32 | // next time should be correct 33 | next, err = nd.GetNextNotificationTime() 34 | check(t, err) 35 | if next != notTime { 36 | t.Fatalf("wrong next time. %s != %s", next, notTime) 37 | } 38 | 39 | // make sure only one due 40 | due, err := nd.GetDueNotifications() 41 | check(t, err) 42 | if len(due) != 1 { 43 | t.Fatalf("Wrong number of due notifications. %d != %d", len(due), 1) 44 | } 45 | 46 | // next time should still be correct 47 | next, err = nd.GetNextNotificationTime() 48 | check(t, err) 49 | if next != notTime { 50 | t.Fatalf("wrong next time. %s != %s", next, notTime) 51 | } 52 | 53 | check(t, nd.ClearNotificationsBefore(notTime)) 54 | // next time should be 1 minute 55 | next, err = nd.GetNextNotificationTime() 56 | check(t, err) 57 | if next != oneMin { 58 | t.Fatalf("wrong next time. %s != %s", next, oneMin) 59 | } 60 | 61 | check(t, nd.ClearNotifications(models.AlertKey("notak{foo=b}"))) 62 | // next time should be 1 hour 63 | next, err = nd.GetNextNotificationTime() 64 | check(t, err) 65 | if next != future { 66 | t.Fatalf("wrong next time. %s != %s", next, future) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/search_data_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | func TestSearch_Metric_RoundTrip(t *testing.T) { 11 | host := randString(5) 12 | err := testData.Search().AddMetricForTag("host", host, "os.cpu", 42) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | metrics, err := testData.Search().GetMetricsForTag("host", host) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | if time, ok := metrics["os.cpu"]; !ok { 22 | t.Fatal("Expected to find os.cpu. I didn't.") 23 | } else if time != 42 { 24 | t.Fatalf("Expected timestamp of 42. Got %d", time) 25 | } 26 | } 27 | 28 | func TestSearch_MetricTagSets(t *testing.T) { 29 | if err := testData.Search().AddMetricTagSet("services.status", "host=abc,service=def", 42); err != nil { 30 | t.Fatal(err) 31 | } 32 | if err := testData.Search().AddMetricTagSet("os.cpu", "host=abc,service=def", 42); err != nil { 33 | t.Fatal(err) 34 | } 35 | if err := testData.Search().AddMetricTagSet("services.status", "host=abc,service=ghi", 42); err != nil { 36 | t.Fatal(err) 37 | } 38 | if err := testData.Search().AddMetricTagSet("services.status", "host=rrr,service=def", 42); err != nil { 39 | t.Fatal(err) 40 | } 41 | tagsets, err := testData.Search().GetMetricTagSets("services.status", opentsdb.TagSet{"host": "abc"}) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if len(tagsets) != 2 { 46 | t.Fatalf("Expected 2 tagsets. Found %d.", len(tagsets)) 47 | } 48 | } 49 | 50 | func TestSearch_MetricTagSets_Scan(t *testing.T) { 51 | for i := int64(0); i < 10000; i++ { 52 | if err := testData.Search().AddMetricTagSet("metric", fmt.Sprintf("host=abc%d", i), i); err != nil { 53 | t.Fatal(err) 54 | } 55 | } 56 | tagsets, err := testData.Search().GetMetricTagSets("metric", nil) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if len(tagsets) != 10000 { 61 | t.Fatalf("Expected 10000 tagsets. Found %d.", len(tagsets)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/silence_test.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "bosun.org/models" 8 | ) 9 | 10 | func TestSilence(t *testing.T) { 11 | sd := testData.Silence() 12 | 13 | silence := &models.Silence{ 14 | Start: time.Now().Add(-48 * time.Hour), 15 | End: time.Now().Add(5 * time.Hour), 16 | Alert: "Foo", 17 | } 18 | future := &models.Silence{ 19 | Start: time.Now().Add(1 * time.Hour), 20 | End: time.Now().Add(2 * time.Hour), 21 | Alert: "Foo", 22 | } 23 | past := &models.Silence{ 24 | Start: time.Now().Add(-48 * time.Hour), 25 | End: time.Now().Add(-5 * time.Hour), 26 | Alert: "Foo", 27 | } 28 | 29 | check(t, sd.AddSilence(silence)) 30 | check(t, sd.AddSilence(past)) 31 | check(t, sd.AddSilence(future)) 32 | 33 | active, err := sd.GetActiveSilences() 34 | check(t, err) 35 | if len(active) != 1 { 36 | t.Fatalf("Expected only one active silence. Got %d.", len(active)) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/testSetup.go: -------------------------------------------------------------------------------- 1 | package dbtest 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | "bosun.org/cmd/bosun/database" 12 | ) 13 | 14 | var flagReddisHost = flag.String("redis", "", "redis server to test against") 15 | var flagFlushRedis = flag.Bool("flush", false, "flush database before tests. DANGER!") 16 | 17 | func StartTestRedis(port int) (database.DataAccess, func()) { 18 | flag.Parse() 19 | // For redis tests we just point at an external server. 20 | if *flagReddisHost != "" { 21 | testData := database.NewDataAccess([]string{*flagReddisHost}, true, "", 0, "") 22 | if *flagFlushRedis { 23 | log.Println("FLUSHING REDIS") 24 | c := testData.(database.RedisConnector).Get() 25 | defer c.Close() 26 | _, err := c.Do("FLUSHDB") 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | } 31 | return testData, func() {} 32 | } 33 | // To test ledis, start a local instance in a new tmp dir. We will attempt to delete it when we're done. 34 | addr := fmt.Sprintf("127.0.0.1:%d", port) 35 | testPath := filepath.Join(os.TempDir(), "bosun_ledis_test", fmt.Sprintf("%d-%d", time.Now().UnixNano(), port)) 36 | log.Println("Test ledis at", testPath, addr) 37 | stop, err := database.StartLedis(testPath, addr) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | testData := database.NewDataAccess([]string{addr}, false, "", 0, "") 42 | return testData, func() { 43 | stop() 44 | os.RemoveAll(testPath) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/bosun/database/test/util/purge_search_data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "bosun.org/cmd/bosun/database" 8 | ) 9 | 10 | // USAGE: go run purge_search_data.go -r redishost:6379 -m metricIWantToClear 11 | // use -n to see what would get deleted, without changing any data 12 | 13 | var redis = flag.String("r", "", "redis host:port") 14 | var metric = flag.String("m", "", "metric to purge") 15 | var noop = flag.Bool("n", false, "only print commands, don't run.") 16 | 17 | func main() { 18 | flag.Parse() 19 | if *redis == "" || *metric == "" { 20 | flag.PrintDefaults() 21 | return 22 | } 23 | db := database.NewDataAccess([]string{*redis}, true, "", 0, "").(interface { 24 | PurgeSearchData(string, bool) error 25 | }) 26 | err := db.PurgeSearchData(*metric, *noop) 27 | fmt.Println(err) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/bosun/dev.sample.conf: -------------------------------------------------------------------------------- 1 | notification default { 2 | email = john@example.org 3 | print = true 4 | } 5 | 6 | template generic { 7 | body = `Acknowledge alert 8 |

Alert definition: 9 |

Name: {{.Alert.Name}} 10 |

Crit: {{.Alert.Crit}} 11 | 12 |

Tags 13 | 14 | 15 | {{range $k, $v := .Group}} 16 | {{if eq $k "host"}} 17 | 18 | {{else}} 19 | 20 | {{end}} 21 | {{end}} 22 |
{{$k}}{{$v}}
{{$k}}{{$v}}
23 | 24 |

Computation 25 | 26 | 27 | {{range .Computations}} 28 | 29 | {{end}} 30 |
{{.Text}}{{.Value}}
` 31 | subject = {{.Last.Status}}: {{.Alert.Name}}: {{.Eval .Alert.Vars.q}} on {{.Group.host}} 32 | } 33 | 34 | alert example.opentsdb.os.high.cpu { 35 | template = generic 36 | $q = avg(q("avg:rate{counter,,1}:os.cpu{host=*}", "2m", "")) 37 | warn = $q > 20 38 | crit = $q >= 1 39 | } 40 | 41 | lookup cpu { 42 | entry host=a,remote=b { 43 | high = 1 44 | } 45 | entry host=*,remote=17* { 46 | high = 4 47 | } 48 | entry host=matts-macbook-pro,remote=* { 49 | high = 2 50 | } 51 | entry host=*,remote=* { 52 | high = 3 53 | } 54 | } 55 | 56 | alert example.opentsdb.cpu.lookup { 57 | crit = lookup("cpu", "high") 58 | } 59 | 60 | alert example.graphite { 61 | crit = avg(graphite("*.cpu.*.cpu.user", "5m", "", "host..cpu")) > 1 62 | } 63 | -------------------------------------------------------------------------------- /cmd/bosun/expr/perf_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | func TestSlowUnion(t *testing.T) { 11 | ra, rb := buildFakeResults() 12 | e := State{} 13 | e.unjoinedOk = true 14 | x := e.union(ra, rb, "") 15 | if len(x) != 1000 { 16 | t.Errorf("Bad length %d != 1000", len(x)) 17 | } 18 | } 19 | 20 | func buildFakeResults() (ra, rb *Results) { 21 | ra = &Results{} 22 | rb = &Results{} 23 | for i := 0; i < 50000; i++ { 24 | tags := opentsdb.TagSet{} 25 | tags["disk"] = fmt.Sprint("a", i) 26 | tags["host"] = fmt.Sprint("b", i) 27 | if i < 1000 { 28 | ra.Results = append(ra.Results, &Result{Value: Number(0), Group: tags}) 29 | } 30 | rb.Results = append(ra.Results, &Result{Value: Number(0), Group: tags}) 31 | } 32 | return ra, rb 33 | } 34 | 35 | func BenchmarkSlowUnion(b *testing.B) { 36 | e := State{} 37 | e.unjoinedOk = true 38 | ra, rb := buildFakeResults() 39 | b.ResetTimer() 40 | for i := 0; i < b.N; i++ { 41 | e.union(ra, rb, "") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/bosun/expr/prom_test.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/prometheus/prometheus/promql" 8 | ) 9 | 10 | type promQueryTemplateTest struct { 11 | title string 12 | promQueryTemplateData 13 | expect string 14 | } 15 | 16 | var promQueryTemplateTests = []promQueryTemplateTest{ 17 | { 18 | "minimal query", 19 | promQueryTemplateData{ 20 | Metric: "up", 21 | AgFunc: "sum", 22 | }, 23 | "sum( up) by ( )", 24 | }, 25 | { 26 | "query with tags", 27 | promQueryTemplateData{ 28 | Metric: "up", 29 | AgFunc: "sum", 30 | Tags: "namespace,pod", 31 | }, 32 | "sum( up) by ( namespace,pod )", 33 | }, 34 | { 35 | "query with tags and filter", 36 | promQueryTemplateData{ 37 | Metric: "up", 38 | AgFunc: "sum", 39 | Tags: "namespace,pod", 40 | Filter: `service !~ "kubl.*"`, 41 | }, 42 | `sum( up {service !~ "kubl.*"} ) by ( namespace,pod )`, 43 | }, 44 | { 45 | "minimal rate query", 46 | promQueryTemplateData{ 47 | Metric: "up", 48 | AgFunc: "sum", 49 | RateDuration: "5m", 50 | }, 51 | `sum(rate( up [5m] )) by ( )`, 52 | }, 53 | { 54 | "rate query with tags and filter", 55 | promQueryTemplateData{ 56 | Metric: "up", 57 | AgFunc: "sum", 58 | Tags: "namespace,pod", 59 | Filter: ` service !~ "kubl.*" `, 60 | RateDuration: "5m", 61 | }, 62 | `sum(rate( up { service !~ "kubl.*" } [5m] )) by ( namespace,pod )`, 63 | }, 64 | } 65 | 66 | func TestPromQueryTemplate(t *testing.T) { 67 | for _, qTest := range promQueryTemplateTests { 68 | out, err := qTest.RenderString() 69 | if err != nil { 70 | t.Errorf("error rendering template for test query %v: %v", qTest.title, err) 71 | continue 72 | } 73 | trimmedOut := strings.TrimSpace(out) 74 | if trimmedOut != qTest.expect { 75 | t.Errorf("unexpected output for test query %v: got ```%v``` want ```%v```", qTest.title, trimmedOut, qTest.expect) 76 | } 77 | _, err = promql.ParseExpr(trimmedOut) 78 | if err != nil { 79 | t.Errorf("failed to parse output of for test query %v as valid promql: %v", qTest, err) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cmd/bosun/ping/ping.go: -------------------------------------------------------------------------------- 1 | package ping // import "bosun.org/cmd/bosun/ping" 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | fastping "github.com/tatsushid/go-fastping" 8 | 9 | "bosun.org/cmd/bosun/search" 10 | "bosun.org/collect" 11 | "bosun.org/metadata" 12 | "bosun.org/opentsdb" 13 | "bosun.org/slog" 14 | ) 15 | 16 | func init() { 17 | metadata.AddMetricMeta("bosun.ping.resolved", metadata.Gauge, metadata.Bool, 18 | "1=Ping resolved to an IP Address. 0=Ping failed to resolve to an IP Address.") 19 | metadata.AddMetricMeta("bosun.ping.rtt", metadata.Gauge, metadata.MilliSecond, 20 | "The number of milliseconds for the echo reply to be received. Also known as Round Trip Time.") 21 | metadata.AddMetricMeta("bosun.ping.timeout", metadata.Gauge, metadata.Ok, 22 | "0=Ping responded before timeout. 1=Ping did not respond before 5 second timeout.") 23 | } 24 | 25 | const pingFreq = time.Second * 15 26 | 27 | // PingHosts pings all hosts that bosun has indexed as recently as the PingDuration 28 | // provided via the systemConf 29 | func PingHosts(search *search.Search, duration time.Duration) { 30 | for range time.Tick(pingFreq) { 31 | hosts, err := search.TagValuesByTagKey("host", duration) 32 | if err != nil { 33 | slog.Error(err) 34 | continue 35 | } 36 | for _, host := range hosts { 37 | go pingHost(host) 38 | } 39 | } 40 | } 41 | 42 | func pingHost(host string) { 43 | p := fastping.NewPinger() 44 | tags := opentsdb.TagSet{"dst_host": host} 45 | resolved := 0 46 | defer func() { 47 | collect.Put("ping.resolved", tags, resolved) 48 | }() 49 | ra, err := net.ResolveIPAddr("ip4:icmp", host) 50 | if err != nil { 51 | return 52 | } 53 | resolved = 1 54 | p.AddIPAddr(ra) 55 | p.MaxRTT = time.Second * 5 56 | timeout := 1 57 | p.OnRecv = func(addr *net.IPAddr, t time.Duration) { 58 | collect.Put("ping.rtt", tags, float64(t)/float64(time.Millisecond)) 59 | timeout = 0 60 | } 61 | if err := p.Run(); err != nil { 62 | slog.Errorln(err) 63 | } 64 | collect.Put("ping.timeout", tags, timeout) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/bosun/sched/esquery2.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "bosun.org/cmd/bosun/expr" 7 | elastic "gopkg.in/olivere/elastic.v3" 8 | ) 9 | 10 | func (c *Context) esQuery2(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 11 | newFilter := expr.ScopeES2(c.Group(), filter.Query(expr.ESV2).(elastic.Query)) 12 | req, err := expr.ESBaseQuery2(c.runHistory.Start, indexRoot, newFilter, sduration, eduration, size, c.ElasticHost) 13 | if err != nil { 14 | c.addError(err) 15 | return nil 16 | } 17 | results, err := c.runHistory.Backends.ElasticHosts.Query2(req) 18 | if err != nil { 19 | c.addError(err) 20 | return nil 21 | } 22 | r := make([]interface{}, len(results.Hits.Hits)) 23 | for i, h := range results.Hits.Hits { 24 | var err error 25 | err = json.Unmarshal(*h.Source, &r[i]) 26 | if err != nil { 27 | c.addError(err) 28 | return nil 29 | } 30 | } 31 | return r 32 | } 33 | 34 | func (c *Context) esQueryAll2(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 35 | req, err := expr.ESBaseQuery2(c.runHistory.Start, indexRoot, filter.Query(expr.ESV2).(elastic.Query), sduration, eduration, size, c.ElasticHost) 36 | if err != nil { 37 | c.addError(err) 38 | return nil 39 | } 40 | results, err := c.runHistory.Backends.ElasticHosts.Query2(req) 41 | if err != nil { 42 | c.addError(err) 43 | return nil 44 | } 45 | r := make([]interface{}, len(results.Hits.Hits)) 46 | for i, h := range results.Hits.Hits { 47 | var err error 48 | err = json.Unmarshal(*h.Source, &r[i]) 49 | if err != nil { 50 | c.addError(err) 51 | return nil 52 | } 53 | } 54 | return r 55 | } 56 | -------------------------------------------------------------------------------- /cmd/bosun/sched/esquery5.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "bosun.org/cmd/bosun/expr" 7 | elastic "gopkg.in/olivere/elastic.v5" 8 | ) 9 | 10 | func (c *Context) esQuery5(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 11 | newFilter := expr.ScopeES5(c.Group(), filter.Query(expr.ESV5).(elastic.Query)) 12 | req, err := expr.ESBaseQuery5(c.runHistory.Start, indexRoot, newFilter, sduration, eduration, size, c.ElasticHost) 13 | if err != nil { 14 | c.addError(err) 15 | return nil 16 | } 17 | results, err := c.runHistory.Backends.ElasticHosts.Query5(req) 18 | if err != nil { 19 | c.addError(err) 20 | return nil 21 | } 22 | r := make([]interface{}, len(results.Hits.Hits)) 23 | for i, h := range results.Hits.Hits { 24 | var err error 25 | err = json.Unmarshal(*h.Source, &r[i]) 26 | if err != nil { 27 | c.addError(err) 28 | return nil 29 | } 30 | } 31 | return r 32 | } 33 | 34 | func (c *Context) esQueryAll5(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 35 | req, err := expr.ESBaseQuery5(c.runHistory.Start, indexRoot, filter.Query(expr.ESV5).(elastic.Query), sduration, eduration, size, c.ElasticHost) 36 | if err != nil { 37 | c.addError(err) 38 | return nil 39 | } 40 | results, err := c.runHistory.Backends.ElasticHosts.Query5(req) 41 | if err != nil { 42 | c.addError(err) 43 | return nil 44 | } 45 | r := make([]interface{}, len(results.Hits.Hits)) 46 | for i, h := range results.Hits.Hits { 47 | var err error 48 | err = json.Unmarshal(*h.Source, &r[i]) 49 | if err != nil { 50 | c.addError(err) 51 | return nil 52 | } 53 | } 54 | return r 55 | } 56 | -------------------------------------------------------------------------------- /cmd/bosun/sched/esquery6.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "bosun.org/cmd/bosun/expr" 7 | elastic "github.com/olivere/elastic" 8 | ) 9 | 10 | func (c *Context) esQuery6(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 11 | newFilter := expr.ScopeES6(c.Group(), filter.Query(expr.ESV6).(elastic.Query)) 12 | req, err := expr.ESBaseQuery6(c.runHistory.Start, indexRoot, newFilter, sduration, eduration, size, c.ElasticHost) 13 | if err != nil { 14 | c.addError(err) 15 | return nil 16 | } 17 | results, err := c.runHistory.Backends.ElasticHosts.Query6(req) 18 | if err != nil { 19 | c.addError(err) 20 | return nil 21 | } 22 | r := make([]interface{}, len(results.Hits.Hits)) 23 | for i, h := range results.Hits.Hits { 24 | var err error 25 | err = json.Unmarshal(*h.Source, &r[i]) 26 | if err != nil { 27 | c.addError(err) 28 | return nil 29 | } 30 | } 31 | return r 32 | } 33 | 34 | func (c *Context) esQueryAll6(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 35 | req, err := expr.ESBaseQuery6(c.runHistory.Start, indexRoot, filter.Query(expr.ESV6).(elastic.Query), sduration, eduration, size, c.ElasticHost) 36 | if err != nil { 37 | c.addError(err) 38 | return nil 39 | } 40 | results, err := c.runHistory.Backends.ElasticHosts.Query6(req) 41 | if err != nil { 42 | c.addError(err) 43 | return nil 44 | } 45 | r := make([]interface{}, len(results.Hits.Hits)) 46 | for i, h := range results.Hits.Hits { 47 | var err error 48 | err = json.Unmarshal(*h.Source, &r[i]) 49 | if err != nil { 50 | c.addError(err) 51 | return nil 52 | } 53 | } 54 | return r 55 | } 56 | -------------------------------------------------------------------------------- /cmd/bosun/sched/esquery7.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "bosun.org/cmd/bosun/expr" 7 | elastic "github.com/olivere/elastic/v7" 8 | ) 9 | 10 | func (c *Context) esQuery7(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 11 | newFilter := expr.ScopeES7(c.Group(), filter.Query(expr.ESV7).(elastic.Query)) 12 | req, err := expr.ESBaseQuery7(c.runHistory.Start, indexRoot, newFilter, sduration, eduration, size, c.ElasticHost) 13 | if err != nil { 14 | c.addError(err) 15 | return nil 16 | } 17 | results, err := c.runHistory.Backends.ElasticHosts.Query7(req) 18 | if err != nil { 19 | c.addError(err) 20 | return nil 21 | } 22 | r := make([]interface{}, len(results.Hits.Hits)) 23 | for i, h := range results.Hits.Hits { 24 | var err error 25 | err = json.Unmarshal(h.Source, &r[i]) 26 | if err != nil { 27 | c.addError(err) 28 | return nil 29 | } 30 | } 31 | return r 32 | } 33 | 34 | func (c *Context) esQueryAll7(indexRoot expr.ESIndexer, filter expr.ESQuery, sduration, eduration string, size int) interface{} { 35 | req, err := expr.ESBaseQuery7(c.runHistory.Start, indexRoot, filter.Query(expr.ESV7).(elastic.Query), sduration, eduration, size, c.ElasticHost) 36 | if err != nil { 37 | c.addError(err) 38 | return nil 39 | } 40 | results, err := c.runHistory.Backends.ElasticHosts.Query7(req) 41 | if err != nil { 42 | c.addError(err) 43 | return nil 44 | } 45 | r := make([]interface{}, len(results.Hits.Hits)) 46 | for i, h := range results.Hits.Hits { 47 | var err error 48 | err = json.Unmarshal(h.Source, &r[i]) 49 | if err != nil { 50 | c.addError(err) 51 | return nil 52 | } 53 | } 54 | return r 55 | } 56 | -------------------------------------------------------------------------------- /cmd/bosun/sched/grouping_test.go: -------------------------------------------------------------------------------- 1 | package sched 2 | 3 | import ( 4 | "testing" 5 | 6 | "bosun.org/models" 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | func TestGroupSets_Single(t *testing.T) { 11 | ak := models.AlertKey("a{host=foo}") 12 | states := States{ak: &models.IncidentState{AlertKey: ak, Alert: "a", Tags: opentsdb.TagSet{"host": "foo"}.Tags(), Subject: "aaa"}} 13 | groups := states.GroupSets(5) 14 | if len(groups) != 1 { 15 | t.Fatalf("Expected 1 group. Found %d.", len(groups)) 16 | } 17 | if len(groups["a{host=foo}"]) == 0 { 18 | t.Fatal("Expected alert key but couldn't find it.") 19 | } 20 | } 21 | 22 | func TestGroupSets_AboveAndBelow(t *testing.T) { 23 | aks := map[string]string{ 24 | "a1{host=a}": "a1 on a", 25 | "a2{host=a}": "a2 on a", 26 | "a3{host=a}": "a3 on a", 27 | "a4{host=a}": "a4 on a", 28 | } 29 | states := States{} 30 | for a, sub := range aks { 31 | ak, err := models.ParseAlertKey(a) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | states[ak] = &models.IncidentState{AlertKey: models.AlertKey(a), Alert: ak.Name(), Tags: ak.Group().Tags(), Subject: sub} 36 | } 37 | 38 | groups := states.GroupSets(5) 39 | if len(groups) != 4 { 40 | t.Fatalf("Expected 4 unique groups, but found %d.", len(groups)) 41 | } 42 | 43 | groups = states.GroupSets(4) 44 | if len(groups) != 1 { 45 | t.Fatalf("Expected 1 unique group, but found %d.", len(groups)) 46 | } 47 | } 48 | 49 | func TestGroupSets_ByAlert(t *testing.T) { 50 | aks := map[string]string{ 51 | "a{host=a}": "a on a", 52 | "a{host=b}": "a on b", 53 | "a{host=c}": "a on c", 54 | "a{host=d}": "a on d", 55 | } 56 | states := States{} 57 | for a, sub := range aks { 58 | ak, err := models.ParseAlertKey(a) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | states[ak] = &models.IncidentState{AlertKey: models.AlertKey(a), Alert: ak.Name(), Tags: ak.Group().Tags(), Subject: sub} 63 | } 64 | 65 | groups := states.GroupSets(5) 66 | if len(groups) != 4 { 67 | t.Fatalf("Expected 4 unique groups, but found %d.", len(groups)) 68 | } 69 | 70 | groups = states.GroupSets(4) 71 | if len(groups) != 1 { 72 | t.Fatalf("Expected 1 unique group, but found %d.", len(groups)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cmd/bosun/syslog_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!nacl,!plan9 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "log/syslog" 9 | ) 10 | 11 | var noSyslog = flag.Bool("disable-syslog", false, "disables logging to syslog") 12 | 13 | func init() { 14 | mains = append(mains, setSyslog) 15 | } 16 | 17 | func setSyslog() { 18 | if *noSyslog || *flagDev || *flagTest { 19 | return 20 | } 21 | w, err := syslog.New(syslog.LOG_LOCAL6|syslog.LOG_INFO, "bosun") 22 | if err != nil { 23 | log.Printf("could not open syslog: %v", err) 24 | return 25 | } 26 | log.Println("enabling syslog") 27 | log.SetOutput(w) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/bosun/w.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while echo "(RE)STARTING BOSUN"; do 4 | go run main.go -w -q -dev || exit 5 | done 6 | -------------------------------------------------------------------------------- /cmd/bosun/web/incident.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "bosun.org/cmd/bosun/sched" 8 | 9 | "github.com/MiniProfiler/go/miniprofiler" 10 | "github.com/kylebrandt/boolq" 11 | ) 12 | 13 | func ListOpenIncidents(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) { 14 | // TODO: Retune this when we no longer store email bodies with incidents 15 | list, err := schedule.DataAccess.State().GetAllOpenIncidents() 16 | if err != nil { 17 | return nil, err 18 | } 19 | suppressor := schedule.Silenced() 20 | if suppressor == nil { 21 | return nil, fmt.Errorf("failed to get silences") 22 | } 23 | summaries := []*sched.IncidentSummaryView{} 24 | filterText := r.FormValue("filter") 25 | var parsedExpr *boolq.Tree 26 | parsedExpr, err = boolq.Parse(filterText) 27 | if err != nil { 28 | return nil, fmt.Errorf("bad filter: %v", err) 29 | } 30 | for _, iState := range list { 31 | is, err := sched.MakeIncidentSummary(schedule.RuleConf, suppressor, iState) 32 | if err != nil { 33 | return nil, err 34 | } 35 | match, err := boolq.AskParsedExpr(parsedExpr, is) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if match { 40 | summaries = append(summaries, is) 41 | } 42 | } 43 | return summaries, nil 44 | } 45 | -------------------------------------------------------------------------------- /cmd/bosun/web/roles_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/captncraig/easyauth" 7 | ) 8 | 9 | func TestWriterRole(t *testing.T) { 10 | if roleWriter&canManageTokens != 0 { 11 | t.Error("Writer should not be able to manage tokens") 12 | } 13 | if roleWriter&canCreateAnnotations != canCreateAnnotations { 14 | t.Error("Writer should be able to create annotations") 15 | } 16 | } 17 | 18 | func TestRoleParse(t *testing.T) { 19 | tests := []struct { 20 | s string 21 | expect easyauth.Role 22 | errors bool 23 | }{ 24 | {"ViewDashboard", canViewDash, false}, 25 | {"ViewDasHBoard", canViewDash, false}, 26 | {"ViewDashboardzzzz", 0, true}, 27 | {"Admin", roleAdmin, false}, 28 | {"ViewDashboard,PutData", canViewDash | canPutData, false}, 29 | {"ViewDashboard,Thisdoesnotexist", 0, true}, 30 | } 31 | for i, test := range tests { 32 | found, err := parseRole(test.s) 33 | if err != nil && !test.errors { 34 | t.Errorf("%d: Unexpected error for %s", i, test.s) 35 | continue 36 | } 37 | if err == nil && test.errors { 38 | t.Errorf("%d: Expected error not found for %s", i, test.s) 39 | continue 40 | } 41 | if found != test.expect { 42 | t.Errorf("%d: Expected %d but got %d", i, test.expect, found) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/css/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/css/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /cmd/bosun/web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/favicon.ico -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /cmd/bosun/web/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo-mark-noborder.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/img/tsaf-logo-mark.png -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/img/tsaf-logo-small.png -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/img/tsaf-logo-text.png -------------------------------------------------------------------------------- /cmd/bosun/web/static/img/tsaf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/bosun/web/static/img/tsaf-logo.png -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | trim_trailing_whitespace = true 4 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/action.ts: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | 4 | interface IActionScope extends IBosunScope { 5 | type: string; 6 | user: string; 7 | message: string; 8 | notify: boolean; 9 | keys: string[]; 10 | submit: () => void; 11 | validateMsg: () => void; 12 | msgValid: boolean; 13 | activeIncidents: boolean; 14 | duration: string; 15 | durationValid: boolean; 16 | } 17 | 18 | bosunControllers.controller('ActionCtrl', ['$scope', '$http', '$location', '$route', function ($scope: IActionScope, $http: ng.IHttpService, $location: ng.ILocationService, $route: ng.route.IRouteService) { 19 | var search = $location.search(); 20 | $scope.type = search.type; 21 | $scope.activeIncidents = search.active == "true"; 22 | $scope.notify = true; 23 | $scope.msgValid = true; 24 | $scope.message = ""; 25 | $scope.duration = ""; 26 | $scope.validateMsg = () => { 27 | $scope.msgValid = (!$scope.notify) || ($scope.message != ""); 28 | } 29 | $scope.durationValid = true; 30 | $scope.validateDuration = () => { 31 | $scope.durationValid = $scope.duration == "" || parseDuration($scope.duration).asMilliseconds() != 0; 32 | } 33 | 34 | if (search.key) { 35 | var keys = search.key; 36 | if (!angular.isArray(search.key)) { 37 | keys = [search.key]; 38 | } 39 | $location.search('key', null); 40 | $scope.setKey('action-keys', keys); 41 | } else { 42 | $scope.keys = $scope.getKey('action-keys'); 43 | } 44 | $scope.submit = () => { 45 | $scope.validateMsg(); 46 | $scope.validateDuration(); 47 | if (!$scope.msgValid || ($scope.user == "") || !$scope.durationValid) { 48 | return; 49 | } 50 | var data = { 51 | Type: $scope.type, 52 | Message: $scope.message, 53 | Keys: $scope.keys, 54 | Notify: $scope.notify, 55 | }; 56 | if ($scope.duration != "") { 57 | data['Time'] = moment.utc().add(parseDuration($scope.duration)); 58 | } 59 | $http.post('/api/action', data) 60 | .success((data) => { 61 | $location.url('/'); 62 | }) 63 | .error((error) => { 64 | alert(error); 65 | }); 66 | }; 67 | }]); -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/angular-sanitize.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Angular JS 1.3 (ngSanitize module) 2 | // Project: http://angularjs.org 3 | // Definitions by: Diego Vilar 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | 7 | /// 8 | 9 | declare module "angular-sanitize" { 10 | var _: string; 11 | export = _; 12 | } 13 | 14 | /////////////////////////////////////////////////////////////////////////////// 15 | // ngSanitize module (angular-sanitize.js) 16 | /////////////////////////////////////////////////////////////////////////////// 17 | declare module angular.sanitize { 18 | 19 | /////////////////////////////////////////////////////////////////////////// 20 | // SanitizeService 21 | // see http://docs.angularjs.org/api/ngSanitize.$sanitize 22 | /////////////////////////////////////////////////////////////////////////// 23 | interface ISanitizeService { 24 | (html: string): string; 25 | } 26 | 27 | /////////////////////////////////////////////////////////////////////////// 28 | // Filters included with the ngSanitize 29 | // see https://github.com/angular/angular.js/tree/v1.2.0/src/ngSanitize/filter 30 | /////////////////////////////////////////////////////////////////////////// 31 | export module filter { 32 | 33 | // Finds links in text input and turns them into html links. 34 | // Supports http/https/ftp/mailto and plain email address links. 35 | // see http://code.angularjs.org/1.2.0/docs/api/ngSanitize.filter:linky 36 | interface ILinky { 37 | (text: string, target?: string): string; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/dashboard.ts: -------------------------------------------------------------------------------- 1 | interface IDashboardScope extends IBosunScope { 2 | error: string; 3 | loading: string; 4 | filter: string; 5 | keydown: any; 6 | } 7 | 8 | bosunControllers.controller('DashboardCtrl', ['$scope', '$http', '$location', function($scope: IDashboardScope, $http: ng.IHttpService, $location: ng.ILocationService) { 9 | var search = $location.search(); 10 | $scope.loading = 'Loading'; 11 | $scope.error = ''; 12 | $scope.filter = search.filter; 13 | if (!$scope.filter) { 14 | $scope.filter = readCookie("filter"); 15 | } 16 | $location.search('filter', $scope.filter || null); 17 | reload(); 18 | function reload() { 19 | $scope.refresh($scope.filter).then(() => { 20 | $scope.loading = ''; 21 | $scope.error = ''; 22 | }, (err: any) => { 23 | $scope.loading = ''; 24 | $scope.error = 'Unable to fetch alerts: ' + err; 25 | }); 26 | } 27 | $scope.keydown = function($event: any) { 28 | if ($event.keyCode == 13) { 29 | createCookie("filter", $scope.filter || "", 1000); 30 | $location.search('filter', $scope.filter || null); 31 | } 32 | } 33 | }]); -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/history.ts: -------------------------------------------------------------------------------- 1 | interface IHistoryScope extends IBosunScope { 2 | ak: string; 3 | alert_history: any; 4 | error: string; 5 | } 6 | 7 | bosunApp.directive('tsAlertHistory', () => { 8 | return { 9 | templateUrl: '/partials/alerthistory.html', 10 | }; 11 | }); 12 | 13 | bosunControllers.controller('HistoryCtrl', ['$scope', '$http', '$location', '$route', function($scope: IHistoryScope, $http: ng.IHttpService, $location: ng.ILocationService, $route: ng.route.IRouteService) { 14 | var search = $location.search(); 15 | var keys: any = {}; 16 | if (angular.isArray(search.key)) { 17 | angular.forEach(search.key, function(v) { 18 | keys[v] = true; 19 | }); 20 | } else { 21 | keys[search.key] = true; 22 | } 23 | var params = Object.keys(keys).map((v: any) => { return 'ak=' + encodeURIComponent(v); }).join('&'); 24 | $http.get('/api/status?' + params + "&all=1") 25 | .success((data) => { 26 | console.log(data); 27 | var selected_alerts: any = {}; 28 | angular.forEach(data, function(v, ak) { 29 | if (!keys[ak]) { 30 | return; 31 | } 32 | v.Events.map((h: any) => { h.Time = moment.utc(h.Time); }); 33 | angular.forEach(v.Events, function(h: any, i: number) { 34 | if (i + 1 < v.Events.length) { 35 | h.EndTime = v.Events[i + 1].Time; 36 | } else { 37 | h.EndTime = moment.utc(); 38 | } 39 | }); 40 | selected_alerts[ak] = { 41 | History: v.Events.reverse(), 42 | }; 43 | }); 44 | if (Object.keys(selected_alerts).length > 0) { 45 | $scope.alert_history = selected_alerts; 46 | } else { 47 | $scope.error = 'No Matching Alerts Found'; 48 | } 49 | }) 50 | .error(err => { 51 | $scope.error = err; 52 | }); 53 | }]); 54 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/items.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface IItemsScope extends ng.IScope { 4 | metrics: string[]; 5 | hosts: string[]; 6 | filterMetrics: string; 7 | filterHosts: string; 8 | status: string; 9 | } 10 | 11 | bosunControllers.controller('ItemsCtrl', ['$scope', '$http', function($scope: IItemsScope, $http: ng.IHttpService) { 12 | $http.get('/api/metric') 13 | .success(function(data: string[]) { 14 | $scope.metrics = data; 15 | }) 16 | .error(function(error) { 17 | $scope.status = 'Unable to fetch metrics: ' + error; 18 | }); 19 | $http.get('/api/tagv/host?since=default') 20 | .success(function(data: string[]) { 21 | $scope.hosts = data; 22 | }) 23 | .error(function(error) { 24 | $scope.status = 'Unable to fetch hosts: ' + error; 25 | }); 26 | }]); 27 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/linkService.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | class LinkService implements ILinkService { 4 | public GetEditSilenceLink(silence: any, silenceId: string) : string { 5 | if (!(silence && silenceId)) { 6 | return ""; 7 | } 8 | 9 | var forget = silence.Forget ? '&forget': ''; 10 | return "/silence?start=" + this.time(silence.Start) + 11 | "&end=" + this.time(silence.End) + 12 | "&alert=" + silence.Alert + 13 | "&tags=" + encodeURIComponent(silence.TagString) + 14 | forget + 15 | "&edit=" + silenceId; 16 | } 17 | 18 | private time(v: any) { 19 | var m = moment(v).utc(); 20 | return m.format(); 21 | } 22 | } 23 | 24 | bosunApp.service("linkService", LinkService); -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/moment-duration-format.d.ts: -------------------------------------------------------------------------------- 1 | interface Duration { 2 | format(template?: any, precision?: any, settings?: any): any; 3 | } -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/ngclipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! ngclipboard - v1.1.1 - 2016-02-26 2 | * https://github.com/sachinchoolur/ngclipboard 3 | * Copyright (c) 2016 Sachin; Licensed MIT */ 4 | !function(){"use strict";var a,b,c="ngclipboard";"object"==typeof module&&module.exports?(a=require("angular"),b=require("clipboard"),module.exports=c):(a=window.angular,b=window.Clipboard),a.module(c,[]).directive("ngclipboard",function(){return{restrict:"A",scope:{ngclipboardSuccess:"&",ngclipboardError:"&"},link:function(a,c){var d=new b(c[0]);d.on("success",function(b){a.$apply(function(){a.ngclipboardSuccess({e:b})})}),d.on("error",function(b){a.$apply(function(){a.ngclipboardError({e:b})})})}}})}(); -------------------------------------------------------------------------------- /cmd/bosun/web/static/js/tokenNew.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | class NewTokenController { 4 | public token: Token = new Token(); 5 | public permissions: Array; 6 | public roles: Array; 7 | public status: string; 8 | 9 | public createdToken: string; 10 | 11 | public hasBits = (bits: number) => { 12 | return (bits & this.token.Role) != 0; 13 | } 14 | 15 | public setRole = (bits: number, event: any) => { 16 | _(this.permissions).each((perm) => { 17 | if (!event.currentTarget.checked) { 18 | perm.Active = false; 19 | } else { 20 | perm.Active = (perm.Bits & bits) != 0; 21 | } 22 | }); 23 | } 24 | 25 | public getBits = () => { 26 | return _(this.permissions).reduce((sum, p) => sum + (p.Active ? p.Bits : 0), 0) 27 | } 28 | 29 | public create() { 30 | this.token.Role = this.getBits(); 31 | this.status = "Creating..." 32 | 33 | this.$http.post("/api/tokens", this.token).then( 34 | (resp: ng.IHttpPromiseCallbackArg) => { 35 | this.status = ""; 36 | this.createdToken = resp.data.replace(/"/g, "") 37 | }, 38 | (err) => { this.status = 'Unable to load roles: ' + err; } 39 | ) 40 | } 41 | 42 | public encoded() { 43 | return encodeURIComponent(this.createdToken) 44 | } 45 | 46 | static $inject = ['$http', 'authService']; 47 | constructor(private $http: ng.IHttpService, private auth: IAuthService) { 48 | var defs = auth.GetRoles(); 49 | this.permissions = defs.Permissions; 50 | this.roles = defs.Roles; 51 | } 52 | } 53 | 54 | bosunApp.component("newToken", { 55 | controller: NewTokenController, 56 | controllerAs: "ct", 57 | templateUrl: "/partials/tokenNew.html" 58 | }) -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/ack.html: -------------------------------------------------------------------------------- 1 | Acknowledge -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/alerthistory.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/cancelClose.html: -------------------------------------------------------------------------------- 1 | Cancel Delayed Close -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/close.html: -------------------------------------------------------------------------------- 1 | Close -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/computations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Value
11 | 12 |
20 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 35 |
36 |
-------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/errors.html: -------------------------------------------------------------------------------- 1 |

Errors

2 | 3 |
4 |
5 |

 6 | 	
7 |
8 |
9 |
10 |
11 | Loading... 12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 |

alert {{err.Name}} - ({{err.Sum}} events) 25 | 28 |

29 |
30 |
31 | 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 | No errors. This is a good thing. 43 |
44 |
45 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/forceClose.html: -------------------------------------------------------------------------------- 1 | Force Close -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/forget.html: -------------------------------------------------------------------------------- 1 | Forget -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/history.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
-------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/items.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Metrics

7 |
8 |
9 | 10 |
11 |
12 |
    13 |
  • 14 |
15 |
16 |
17 |

Hosts

18 |
19 |
20 | 21 |
22 |
23 |
    24 |
  • 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/note.html: -------------------------------------------------------------------------------- 1 | Add Note -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/notification.html: -------------------------------------------------------------------------------- 1 |
2 |
To:
3 |
4 |
{{ct.dat.To}}
5 |
6 |
7 |
8 |
URL:
9 |
{{ct.dat.URL}}
10 |
11 |
12 |
Subject:
13 |
14 |
{{ct.dat.Subject}}
15 |
16 |
17 |
18 |
Body:
19 |
20 |
{{ct.dat.Body}}
21 |
22 |
23 |
24 |
Print:
25 |
26 |
{{ct.dat.Print}}
27 |
28 |
29 |
30 |
31 |
Headers:
32 |
33 |
34 |
{{k}}:
35 |
{{v}}
36 |
37 |
38 |
39 |
{{ct.dat.msg}}
40 |
-------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/purge.html: -------------------------------------------------------------------------------- 1 | Purge -------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/results.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Executing: {{running}}
9 |
10 |
11 |
12 |
13 |

Queries

14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 42 | 43 | 44 | 45 |
groupresultcomputations
32 | { 33 | 34 | {{k}}={{v}}, 35 | 36 | } 37 | 39 | 40 |

41 | 					
46 |
47 |
-------------------------------------------------------------------------------- /cmd/bosun/web/static/partials/tokenList.html: -------------------------------------------------------------------------------- 1 |
{{ct.status}}
2 |

Access Tokens

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | 28 |
IDUserDescriptionPermissionsLast Used
{{tok.Hash | limitTo: 6}}{{tok.User}}{{tok.Description}} 20 | {{tok.RoleName}} 21 | Never 24 | 25 |
29 | Create new token 30 | 31 | -------------------------------------------------------------------------------- /cmd/scollector/README.md: -------------------------------------------------------------------------------- 1 | # scollector 2 | 3 | scollector is a replacement for OpenTSDB's tcollector. 4 | 5 | Benefits of scollector over tcollector: 6 | 7 | - uses the OpenTSDB v2 API, not the older v1 API 8 | - more resource efficient 9 | - integrates with [Bosun](http://bosun.org) 10 | - comes with many collectors for Windows, Linux, and Mac 11 | 12 | OpenTSDB 1.0 uses [tcollector](https://github.com/OpenTSDB/tcollector) to 13 | collect data. This project aims to make scollector the preferred collector for 14 | OpenTSDB 2.0. 15 | 16 | ## documentation 17 | 18 | [http://godoc.org/bosun.org/cmd/scollector](http://godoc.org/bosun.org/cmd/scollector) 19 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/collectors_test.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "testing" 5 | 6 | "bosun.org/host" 7 | "bosun.org/util" 8 | 9 | "bosun.org/opentsdb" 10 | ) 11 | 12 | func TestIsDigit(t *testing.T) { 13 | numbers := []string{"029", "1", "400"} 14 | not_numbers := []string{"1a3", " 3", "-1", "3.0", "am"} 15 | for _, s := range not_numbers { 16 | if IsDigit(s) { 17 | t.Errorf("%s: not expected to be a digit", s) 18 | } 19 | } 20 | for _, n := range numbers { 21 | if !IsDigit(n) { 22 | t.Errorf("%s: expected to be a digit", n) 23 | } 24 | } 25 | } 26 | 27 | func TestAddTS_Invalid(t *testing.T) { 28 | hm, err := host.NewManager(false) 29 | if err != nil { 30 | t.Error(err) 31 | } 32 | 33 | util.SetHostManager(hm) 34 | 35 | mdp := &opentsdb.MultiDataPoint{} 36 | ts := opentsdb.TagSet{"srv": "%%%"} 37 | Add(mdp, "aaaa", 42, ts, "", "", "") //don't have a good way to tesst this automatically, but I look for a log message with this line number in it. 38 | if len(*mdp) != 0 { 39 | t.Fatal("Shouldn't have added invalid tags.") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/conntrack_linux.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | 8 | "bosun.org/metadata" 9 | "bosun.org/opentsdb" 10 | ) 11 | 12 | func init() { 13 | collectors = append(collectors, &IntervalCollector{F: c_conntrack_linux, Enable: conntrackEnable}) 14 | } 15 | 16 | const ( 17 | conntrackCount = "/proc/sys/net/netfilter/nf_conntrack_count" 18 | conntrackMax = "/proc/sys/net/netfilter/nf_conntrack_max" 19 | ) 20 | 21 | func conntrackEnable() bool { 22 | f, err := os.Open(conntrackCount) 23 | defer f.Close() 24 | return err == nil 25 | } 26 | 27 | func c_conntrack_linux() (opentsdb.MultiDataPoint, error) { 28 | var md opentsdb.MultiDataPoint 29 | var max, count float64 30 | if err := readLine(conntrackCount, func(s string) error { 31 | values := strings.Fields(s) 32 | if len(values) > 0 { 33 | var err error 34 | count, err = strconv.ParseFloat(values[0], 64) 35 | if err != nil { 36 | return nil 37 | } 38 | Add(&md, "linux.net.conntrack.count", count, nil, metadata.Gauge, metadata.Count, "") 39 | } 40 | return nil 41 | }); err != nil { 42 | return nil, err 43 | } 44 | if err := readLine(conntrackMax, func(s string) error { 45 | values := strings.Fields(s) 46 | if len(values) > 0 { 47 | var err error 48 | max, err = strconv.ParseFloat(values[0], 64) 49 | if err != nil { 50 | return nil 51 | } 52 | Add(&md, "linux.net.conntrack.max", max, nil, metadata.Gauge, metadata.Count, "") 53 | } 54 | return nil 55 | }); err != nil { 56 | return nil, err 57 | } 58 | if max != 0 { 59 | Add(&md, "linux.net.conntrack.percent_used", count/max*100, nil, metadata.Gauge, metadata.Pct, "") 60 | } 61 | return md, nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/dfstat_darwin.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "bosun.org/metadata" 8 | "bosun.org/opentsdb" 9 | "bosun.org/util" 10 | ) 11 | 12 | func init() { 13 | collectors = append(collectors, &IntervalCollector{F: c_dfstat_darwin}) 14 | } 15 | 16 | func c_dfstat_darwin() (opentsdb.MultiDataPoint, error) { 17 | var md opentsdb.MultiDataPoint 18 | util.ReadCommand(func(line string) error { 19 | fields := strings.Fields(line) 20 | if line == "" || len(fields) < 9 || !IsDigit(fields[2]) { 21 | return nil 22 | } 23 | mount := fields[8] 24 | if strings.HasPrefix(mount, "/Volumes/Time Machine Backups") { 25 | return nil 26 | } 27 | f5, _ := strconv.Atoi(fields[5]) 28 | f6, _ := strconv.Atoi(fields[6]) 29 | tags := opentsdb.TagSet{"mount": mount} 30 | Add(&md, "darwin.disk.fs.total", fields[1], tags, metadata.Unknown, metadata.None, "") 31 | Add(&md, "darwin.disk.fs.used", fields[2], tags, metadata.Unknown, metadata.None, "") 32 | Add(&md, "darwin.disk.fs.free", fields[3], tags, metadata.Unknown, metadata.None, "") 33 | Add(&md, "darwin.disk.fs.inodes.total", f5+f6, tags, metadata.Unknown, metadata.None, "") 34 | Add(&md, "darwin.disk.fs.inodes.used", fields[5], tags, metadata.Unknown, metadata.None, "") 35 | Add(&md, "darwin.disk.fs.inodes.free", fields[6], tags, metadata.Unknown, metadata.None, "") 36 | return nil 37 | }, "df", "-lki") 38 | return md, nil 39 | } 40 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/disk_linux_test.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import "testing" 4 | 5 | func TestFilterVolumes(t *testing.T) { 6 | in := []string{"/dev/zero", "/etc/passwd"} 7 | out := []string{"/dev/zero"} 8 | got := filterVolumes(in) 9 | if !compTab(got, out) { 10 | t.Fatalf("filterVolume: got: %v, expected: %v", got, out) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/fake.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "bosun.org/metadata" 8 | "bosun.org/opentsdb" 9 | ) 10 | 11 | func InitFake(fake int) { 12 | collectors = append(collectors, &IntervalCollector{ 13 | F: func() (opentsdb.MultiDataPoint, error) { 14 | var md opentsdb.MultiDataPoint 15 | for i := 0; i < fake; i++ { 16 | Add(&md, "test.fake", i, opentsdb.TagSet{"i": strconv.Itoa(i)}, metadata.Unknown, metadata.None, "") 17 | } 18 | return md, nil 19 | }, 20 | Interval: time.Second, 21 | name: "fake", 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/hi.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "bosun.org/metadata" 5 | "bosun.org/opentsdb" 6 | ) 7 | 8 | func init() { 9 | collectors = append(collectors, &IntervalCollector{F: c_scollector_hi}) 10 | } 11 | 12 | const ( 13 | hiDesc = "Scollector sends a 1 every DefaultFreq. This is so you can alert on scollector being down." 14 | ) 15 | 16 | func c_scollector_hi() (opentsdb.MultiDataPoint, error) { 17 | var md opentsdb.MultiDataPoint 18 | Add(&md, "scollector.hi", 1, nil, metadata.Gauge, metadata.Ok, hiDesc) 19 | return md, nil 20 | } 21 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/icmp.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "bosun.org/metadata" 9 | "bosun.org/opentsdb" 10 | "github.com/tatsushid/go-fastping" 11 | ) 12 | 13 | type response struct { 14 | addr *net.IPAddr 15 | rtt time.Duration 16 | } 17 | 18 | // ICMP registers an ICMP collector a given host. 19 | func ICMP(host string) error { 20 | if host == "" { 21 | return fmt.Errorf("empty ICMP hostname") 22 | } 23 | collectors = append(collectors, &IntervalCollector{ 24 | F: func() (opentsdb.MultiDataPoint, error) { 25 | return c_icmp(host) 26 | }, 27 | name: fmt.Sprintf("icmp-%s", host), 28 | }) 29 | return nil 30 | } 31 | 32 | func c_icmp(host string) (opentsdb.MultiDataPoint, error) { 33 | var md opentsdb.MultiDataPoint 34 | p := fastping.NewPinger() 35 | ra, err := net.ResolveIPAddr("ip4:icmp", host) 36 | if err != nil { 37 | return nil, err 38 | } 39 | p.AddIPAddr(ra) 40 | p.MaxRTT = time.Second * 5 41 | timeout := 1 42 | p.OnRecv = func(addr *net.IPAddr, t time.Duration) { 43 | Add(&md, "ping.rtt", float64(t)/float64(time.Millisecond), opentsdb.TagSet{"dst_host": host}, metadata.Unknown, metadata.None, "") 44 | timeout = 0 45 | } 46 | if err := p.Run(); err != nil { 47 | return nil, err 48 | } 49 | Add(&md, "ping.timeout", timeout, opentsdb.TagSet{"dst_host": host}, metadata.Unknown, metadata.None, "") 50 | return md, nil 51 | } 52 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/metadata_darwin.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "bosun.org/metadata" 9 | "bosun.org/opentsdb" 10 | "bosun.org/util" 11 | ) 12 | 13 | func init() { 14 | collectors = append(collectors, &IntervalCollector{F: c_meta_darwin_version, Interval: time.Minute * 30}) 15 | collectors = append(collectors, &IntervalCollector{F: c_meta_darwin_interfaces, Interval: time.Minute * 30}) 16 | } 17 | 18 | func c_meta_darwin_version() (opentsdb.MultiDataPoint, error) { 19 | var md opentsdb.MultiDataPoint 20 | util.ReadCommand(func(line string) error { 21 | metadata.AddMeta("", nil, "uname", line, true) 22 | return nil 23 | }, "uname", "-a") 24 | var name, vers, build string 25 | util.ReadCommand(func(line string) error { 26 | sp := strings.SplitN(line, ":", 2) 27 | if len(sp) != 2 { 28 | return nil 29 | } 30 | v := strings.TrimSpace(sp[1]) 31 | switch sp[0] { 32 | case "ProductName": 33 | name = v 34 | case "ProductVersion": 35 | vers = v 36 | case "BuildVersion": 37 | build = v 38 | } 39 | return nil 40 | }, "sw_vers") 41 | if name != "" && vers != "" && build != "" { 42 | metadata.AddMeta("", nil, "version", fmt.Sprintf("%s.%s", vers, build), true) 43 | metadata.AddMeta("", nil, "versionCaption", fmt.Sprintf("%s %s", name, vers), true) 44 | } 45 | return md, nil 46 | } 47 | 48 | func c_meta_darwin_interfaces() (opentsdb.MultiDataPoint, error) { 49 | var md opentsdb.MultiDataPoint 50 | metaIfaces(nil) 51 | return md, nil 52 | } 53 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/metadata_linux_test.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import "testing" 4 | 5 | func TestLinuxIpAddrShowMaster(t *testing.T) { 6 | inputs := map[string]string{ 7 | "bond0": `2: em1: mtu 1500 qdisc mq master bond0 state UP qlen 1000\ link/ether bc:30:5b:ed:c0:80 brd ff:ff:ff:ff:ff:ff`, 8 | "bond1": `5: em4: mtu 1500 qdisc mq master bond1 state UP qlen 1000\ link/ether bc:30:5b:ed:c0:3a brd ff:ff:ff:ff:ff:ff`, 9 | "": `7: bond1: mtu 1500 qdisc noqueue state UP \ link/ether bc:30:5b:ed:c0:3a brd ff:ff:ff:ff:ff:ff`, 10 | } 11 | for expect, val := range inputs { 12 | got := metaLinuxIfacesMaster(val) 13 | if got != expect { 14 | t.Errorf("%v: expected %v, got %v", val, expect, got) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/opentsdb.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "bosun.org/metadata" 8 | "bosun.org/opentsdb" 9 | ) 10 | 11 | func init() { 12 | collectors = append(collectors, &IntervalCollector{F: c_opentsdb, Enable: enableURL(tsdbURL)}) 13 | } 14 | 15 | const tsdbURL = "http://localhost:4242/api/stats" 16 | 17 | func c_opentsdb() (opentsdb.MultiDataPoint, error) { 18 | resp, err := http.Get(tsdbURL) 19 | if err != nil { 20 | return nil, err 21 | } 22 | defer resp.Body.Close() 23 | var md, tmp opentsdb.MultiDataPoint 24 | if err := json.NewDecoder(resp.Body).Decode(&tmp); err != nil { 25 | return nil, err 26 | } 27 | for _, v := range tmp { 28 | delete(v.Tags, "host") 29 | AddTS(&md, v.Metric, v.Timestamp, v.Value, v.Tags, metadata.Unknown, metadata.None, "") 30 | } 31 | return md, nil 32 | } 33 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/processes_darwin.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | 6 | "bosun.org/cmd/scollector/conf" 7 | ) 8 | 9 | func AddProcessConfig(params conf.ProcessParams) error { 10 | return fmt.Errorf("process watching not implemented on Darwin") 11 | } 12 | 13 | func WatchProcesses() { 14 | } 15 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/program_linux.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "os/exec" 5 | "syscall" 6 | ) 7 | 8 | func init() { 9 | setupExternalCommand = func(cmd *exec.Cmd) { 10 | cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/puppet_linux.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | const ( 4 | puppetPath = "/var/lib/puppet/" 5 | puppetRunSummary = "/var/lib/puppet/state/last_run_summary.yaml" 6 | puppetRunReport = "/var/lib/puppet/state/last_run_report.yaml" 7 | puppetDisabled = "/var/lib/puppet/state/agent_disabled.lock" 8 | ) 9 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/puppet_windows.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | const ( 4 | puppetPath = `C:\ProgramData\PuppetLabs` 5 | puppetRunSummary = `C:\ProgramData\PuppetLabs\puppet\cache\state\last_run_summary.yaml` 6 | puppetRunReport = `C:\ProgramData\PuppetLabs\puppet\cache\state\last_run_report.yaml` 7 | puppetDisabled = `C:\ProgramData\PuppetLabs\puppet\cache\state\agent_disabled.lock` 8 | ) 9 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/railgun_linux.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "regexp" 7 | "strings" 8 | "time" 9 | 10 | "bosun.org/metadata" 11 | "bosun.org/opentsdb" 12 | "bosun.org/util" 13 | ) 14 | 15 | func init() { 16 | collectors = append(collectors, &IntervalCollector{F: c_railgun, Enable: enableRailgun, Interval: time.Minute}) 17 | } 18 | 19 | var ( 20 | rgListenRE = regexp.MustCompile(`^stats.listen\s+?=\s+?([0-9.:]+)`) 21 | rgURL string 22 | ) 23 | 24 | func parseRailURL() string { 25 | var config string 26 | var url string 27 | util.ReadCommand(func(line string) error { 28 | fields := strings.Fields(line) 29 | if len(fields) == 0 || !strings.Contains(fields[0], "rg-listener") { 30 | return nil 31 | } 32 | for i, s := range fields { 33 | if s == "-config" && len(fields) > i { 34 | config = fields[i+1] 35 | } 36 | } 37 | return nil 38 | }, "ps", "-e", "-o", "args") 39 | if config == "" { 40 | return config 41 | } 42 | readLine(config, func(s string) error { 43 | if m := rgListenRE.FindStringSubmatch(s); len(m) > 0 { 44 | url = "http://" + m[1] 45 | } 46 | return nil 47 | }) 48 | return url 49 | } 50 | 51 | func enableRailgun() bool { 52 | rgURL = parseRailURL() 53 | return enableURL(rgURL)() 54 | } 55 | 56 | func c_railgun() (opentsdb.MultiDataPoint, error) { 57 | var md opentsdb.MultiDataPoint 58 | res, err := http.Get(rgURL) 59 | if err != nil { 60 | return nil, err 61 | } 62 | defer res.Body.Close() 63 | var r map[string]interface{} 64 | j := json.NewDecoder(res.Body) 65 | if err := j.Decode(&r); err != nil { 66 | return nil, err 67 | } 68 | for k, v := range r { 69 | if _, ok := v.(float64); ok { 70 | Add(&md, "railgun."+k, v, nil, metadata.Unknown, metadata.None, "") 71 | } 72 | } 73 | return md, nil 74 | } 75 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/redis_counters.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "bosun.org/cmd/scollector/conf" 9 | "bosun.org/collect" 10 | "bosun.org/metadata" 11 | "bosun.org/models" 12 | "bosun.org/opentsdb" 13 | "bosun.org/slog" 14 | "github.com/garyburd/redigo/redis" 15 | ) 16 | 17 | func init() { 18 | registerInit(func(c *conf.Conf) { 19 | for _, red := range c.RedisCounters { 20 | collectors = append(collectors, 21 | &IntervalCollector{ 22 | name: "redisCounters_" + red.Server, 23 | F: func() (opentsdb.MultiDataPoint, error) { 24 | return c_redis_counters(red.Server, red.Database) 25 | }, 26 | }) 27 | } 28 | }) 29 | } 30 | 31 | func c_redis_counters(server string, db int) (opentsdb.MultiDataPoint, error) { 32 | var md opentsdb.MultiDataPoint 33 | conn, err := redis.Dial("tcp", server, redis.DialDatabase(db)) 34 | if err != nil { 35 | return md, slog.Wrap(err) 36 | } 37 | defer conn.Close() 38 | 39 | //do a dance to detect proper hscan command for ledis or redis 40 | hscanCmd := "XHSCAN" 41 | info, err := redis.String(conn.Do("info", "server")) 42 | if err != nil { 43 | return md, slog.Wrap(err) 44 | } 45 | if strings.Contains(info, "redis_version") { 46 | hscanCmd = "HSCAN" 47 | } 48 | 49 | cursor := "0" 50 | for { 51 | vals, err := redis.Values(conn.Do(hscanCmd, collect.RedisCountersKey, cursor)) 52 | if err != nil { 53 | return md, slog.Wrap(err) 54 | } 55 | if len(vals) != 2 { 56 | return md, fmt.Errorf("Unexpected number of values") 57 | } 58 | cursor, err = redis.String(vals[0], nil) 59 | if err != nil { 60 | return md, slog.Wrap(err) 61 | } 62 | pairs, err := redis.StringMap(vals[1], nil) 63 | if err != nil { 64 | return md, slog.Wrap(err) 65 | } 66 | for key, val := range pairs { 67 | ak := models.AlertKey(key) 68 | 69 | v, err := strconv.Atoi(val) 70 | if err != nil { 71 | slog.Errorf("Invalid counter value: %s", val) 72 | continue 73 | } 74 | Add(&md, ak.Name(), v, ak.Group(), metadata.Counter, metadata.Count, "") 75 | } 76 | if cursor == "" || cursor == "0" { 77 | break 78 | } 79 | } 80 | return md, nil 81 | } 82 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/snmp_lag.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "bosun.org/cmd/scollector/conf" 8 | "bosun.org/metadata" 9 | "bosun.org/opentsdb" 10 | ) 11 | 12 | const ( 13 | dot3adAggPortAttachedAggID = ".1.2.840.10006.300.43.1.2.1.1.13" 14 | ) 15 | 16 | // SNMPLag registers a SNMP Interfaces collector for the given community and host. 17 | func SNMPLag(cfg conf.SNMP) { 18 | collectors = append(collectors, &IntervalCollector{ 19 | F: func() (opentsdb.MultiDataPoint, error) { 20 | return c_snmp_lag(cfg.Community, cfg.Host) 21 | }, 22 | Interval: time.Second * 30, 23 | name: fmt.Sprintf("snmp-lag-%s", cfg.Host), 24 | }) 25 | } 26 | 27 | func c_snmp_lag(community, host string) (opentsdb.MultiDataPoint, error) { 28 | ifNamesRaw, err := snmp_subtree(host, community, dot3adAggPortAttachedAggID) 29 | if err != nil { 30 | return nil, err 31 | } 32 | for k, v := range ifNamesRaw { 33 | tags := opentsdb.TagSet{"host": host, "iface": k} 34 | metadata.AddMeta("", tags, "masterIface", fmt.Sprintf("%v", v), false) 35 | } 36 | return nil, nil 37 | } 38 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/snmp_sys.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "time" 7 | 8 | "bosun.org/cmd/scollector/conf" 9 | "bosun.org/metadata" 10 | "bosun.org/opentsdb" 11 | ) 12 | 13 | const ( 14 | sysUpTime = ".1.3.6.1.2.1.1.3.0" // "The time (in hundredths of a second) since the network management portion of the system was last re-initialized." 15 | sysDescr = ".1.3.6.1.2.1.1.1.0" 16 | ) 17 | 18 | // SNMPSys registers a SNMP system data collector for the given community and host. 19 | func SNMPSys(cfg conf.SNMP) { 20 | collectors = append(collectors, &IntervalCollector{ 21 | F: func() (opentsdb.MultiDataPoint, error) { 22 | return c_snmp_sys(cfg.Host, cfg.Community) 23 | }, 24 | Interval: time.Minute * 1, 25 | name: fmt.Sprintf("snmp-sys-%s", cfg.Host), 26 | }) 27 | } 28 | 29 | func c_snmp_sys(host, community string) (opentsdb.MultiDataPoint, error) { 30 | var md opentsdb.MultiDataPoint 31 | uptime, err := snmp_oid(host, community, sysUpTime) 32 | if err != nil { 33 | return md, err 34 | } 35 | Add(&md, osSystemUptime, uptime.Int64()/big.NewInt(100).Int64(), opentsdb.TagSet{"host": host}, metadata.Gauge, metadata.Second, osSystemUptimeDesc) 36 | return md, nil 37 | } 38 | 39 | // Description may mean different things so it isn't called in sys, for example 40 | // with cisco it is the os version 41 | func getSNMPDesc(host, community string) (description string, err error) { 42 | description, err = snmpOidString(host, community, sysDescr) 43 | if err != nil { 44 | return description, fmt.Errorf("failed to fetch description for host %v: %v", host, err) 45 | } 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/stream.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | 7 | "bosun.org/collect" 8 | "bosun.org/metadata" 9 | "bosun.org/opentsdb" 10 | "bosun.org/util" 11 | ) 12 | 13 | /* StreamCollector is useful for collectors that do not produces metrics at a 14 | preset interval. Instead it consummes directly from a channel provided by 15 | the collector and forwards it internally. */ 16 | 17 | type StreamCollector struct { 18 | F func() <-chan *opentsdb.MultiDataPoint 19 | name string 20 | init func() 21 | 22 | TagOverride 23 | } 24 | 25 | func (s *StreamCollector) Init() { 26 | if s.init != nil { 27 | s.init() 28 | } 29 | } 30 | 31 | func (s *StreamCollector) Run(dpchan chan<- *opentsdb.DataPoint, quit <-chan struct{}) { 32 | inputChan := s.F() 33 | count := 0 34 | for { 35 | select { 36 | case md := <-inputChan: 37 | if !collect.DisableDefaultCollectors { 38 | tags := opentsdb.TagSet{"collector": s.Name(), "os": runtime.GOOS} 39 | Add(md, "scollector.collector.count", count, tags, metadata.Counter, metadata.Count, "Counter of metrics passed through.") 40 | } 41 | 42 | for _, dp := range *md { 43 | if _, found := dp.Tags["host"]; !found { 44 | dp.Tags["host"] = util.GetHostManager().GetHostName() 45 | } 46 | s.ApplyTagOverrides(dp.Tags) 47 | dpchan <- dp 48 | count++ 49 | } 50 | case <-quit: 51 | return 52 | } 53 | } 54 | } 55 | 56 | func (s *StreamCollector) Enabled() bool { 57 | return true 58 | } 59 | 60 | func (s *StreamCollector) Name() string { 61 | if s.name != "" { 62 | return s.name 63 | } 64 | v := runtime.FuncForPC(reflect.ValueOf(s.F).Pointer()) 65 | return v.Name() 66 | 67 | } 68 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/tag_override.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | type TagOverride struct { 11 | matchedTags map[string]*regexp.Regexp 12 | tags opentsdb.TagSet 13 | } 14 | 15 | func (to *TagOverride) AddTagOverrides(sources map[string]string, t opentsdb.TagSet) error { 16 | if to.matchedTags == nil { 17 | to.matchedTags = make(map[string]*regexp.Regexp) 18 | } 19 | var err error 20 | for tag, re := range sources { 21 | to.matchedTags[tag], err = regexp.Compile(re) 22 | if err != nil { 23 | return fmt.Errorf("invalid regexp: %s error: %s", re, err) 24 | } 25 | } 26 | 27 | if to.tags == nil { 28 | to.tags = t.Copy() 29 | } else { 30 | to.tags = to.tags.Merge(t) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (to *TagOverride) ApplyTagOverrides(t opentsdb.TagSet) { 37 | namedMatchGroup := make(map[string]string) 38 | for tag, re := range to.matchedTags { 39 | if v, ok := t[tag]; ok { 40 | matches := re.FindStringSubmatch(v) 41 | 42 | if len(matches) > 1 { 43 | for i, match := range matches[1:] { 44 | matchedTag := re.SubexpNames()[i+1] 45 | if match != "" && matchedTag != "" { 46 | namedMatchGroup[matchedTag] = match 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | for tag, v := range namedMatchGroup { 54 | t[tag] = v 55 | } 56 | 57 | for tag, v := range to.tags { 58 | if v == "" { 59 | delete(t, tag) 60 | } else { 61 | t[tag] = v 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/vmstat_darwin.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "bosun.org/metadata" 8 | "bosun.org/opentsdb" 9 | "bosun.org/util" 10 | ) 11 | 12 | func init() { 13 | collectors = append(collectors, &IntervalCollector{F: c_vmstat_darwin}) 14 | } 15 | 16 | func c_vmstat_darwin() (opentsdb.MultiDataPoint, error) { 17 | var md opentsdb.MultiDataPoint 18 | var free float64 19 | util.ReadCommand(func(line string) error { 20 | if line == "" || strings.HasPrefix(line, "Object cache") || strings.HasPrefix(line, "Mach Virtual") { 21 | return nil 22 | } 23 | fields := strings.Split(line, ":") 24 | if len(fields) < 2 { 25 | return nil 26 | } 27 | value, err := strconv.ParseFloat(strings.TrimSpace(fields[1]), 64) 28 | if err != nil { 29 | return nil 30 | } 31 | if strings.HasPrefix(fields[0], "Pages") { 32 | name := strings.TrimSpace(fields[0]) 33 | name = strings.Replace(name, "Pages ", "", -1) 34 | name = strings.Replace(name, " ", "", -1) 35 | Add(&md, "darwin.mem.vm.4kpages."+name, value, nil, metadata.Unknown, metadata.None, "") 36 | if name == "free" { 37 | free = value * 4096 38 | Add(&md, osMemFree, free, nil, metadata.Gauge, metadata.Bytes, osMemFreeDesc) 39 | } 40 | } else if fields[0] == "Pageins" { 41 | Add(&md, "darwin.mem.vm.pageins", value, nil, metadata.Counter, metadata.None, "") 42 | } else if fields[0] == "Pageouts" { 43 | Add(&md, "darwin.mem.vm.pageouts", value, nil, metadata.Counter, metadata.None, "") 44 | } 45 | return nil 46 | }, "vm_stat") 47 | util.ReadCommand(func(line string) error { 48 | total, _ := strconv.ParseFloat(line, 64) 49 | if total == 0 { 50 | return nil 51 | } 52 | Add(&md, osMemTotal, total, nil, metadata.Gauge, metadata.Bytes, osMemTotalDesc) 53 | if free == 0 { 54 | return nil 55 | } 56 | Add(&md, osMemUsed, total-free, nil, metadata.Gauge, metadata.Bytes, osMemUsedDesc) 57 | Add(&md, osMemPctFree, free/total, nil, metadata.Gauge, metadata.Pct, osMemPctFreeDesc) 58 | return nil 59 | }, "sysctl", "-n", "hw.memsize") 60 | return md, nil 61 | } 62 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/wmi_windows.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/StackExchange/wmi" 9 | ) 10 | 11 | func queryWmi(query string, dst interface{}) error { 12 | return queryWmiNamespace(query, dst, "") 13 | } 14 | 15 | func queryWmiNamespace(query string, dst interface{}, namespace string) error { 16 | return wmi.QueryNamespace(query, dst, namespace) 17 | } 18 | 19 | func wmiInit(c *IntervalCollector, dst func() interface{}, where string, query *string) func() { 20 | return func() { 21 | *query = wmi.CreateQuery(dst(), where) 22 | c.Enable = func() bool { 23 | return queryWmi(*query, dst()) == nil 24 | } 25 | } 26 | } 27 | 28 | func wmiInitNamespace(c *IntervalCollector, dst func() interface{}, where string, query *string, namespace string) func() { 29 | return func() { 30 | *query = wmi.CreateQuery(dst(), where) 31 | c.Enable = func() bool { 32 | return queryWmiNamespace(*query, dst(), namespace) == nil 33 | } 34 | } 35 | } 36 | 37 | // wmiParseDatetime converts a string from the CIM_DATETIME format into UTC time. 38 | // Example: "20150616101948.494497-360" = 2015-06-16 04:19:48.494497 +0000 UTC. 39 | func wmiParseCIMDatetime(cimdatetime string) (time.Time, error) { 40 | i := strings.IndexAny(cimdatetime, "+-") 41 | if i < 0 { 42 | return time.Time{}, fmt.Errorf("Invalid CIM_DATETIME format, cannot find UTC offset.") 43 | } 44 | t, err := time.Parse("20060102150405", cimdatetime[:i]) 45 | if err != nil { 46 | return time.Time{}, err 47 | } 48 | offset, err := time.ParseDuration(fmt.Sprintf("%vm", cimdatetime[i:])) 49 | if err != nil { 50 | return time.Time{}, err 51 | } 52 | return t.Add(offset), nil 53 | } 54 | -------------------------------------------------------------------------------- /cmd/scollector/collectors/yum_update_linux.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "bosun.org/metadata" 8 | "bosun.org/opentsdb" 9 | "bosun.org/util" 10 | ) 11 | 12 | func init() { 13 | collectors = append(collectors, &IntervalCollector{F: yum_update_stats_linux, Interval: time.Minute * 30}) 14 | } 15 | 16 | func yum_update_stats_linux() (opentsdb.MultiDataPoint, error) { 17 | var md opentsdb.MultiDataPoint 18 | regular_c := 0 19 | kernel_c := 0 20 | // This is a silly long timeout, but until we implement sigint this will 21 | // Prevent a currupt yum db https://github.com/bosun-monitor/scollector/issues/56 22 | err := util.ReadCommandTimeout(time.Minute*5, func(line string) error { 23 | fields := strings.Fields(line) 24 | if len(fields) > 1 && !strings.HasPrefix(fields[0], "Updated Packages") { 25 | if strings.HasPrefix(fields[0], "kern") { 26 | kernel_c++ 27 | } else { 28 | regular_c++ 29 | } 30 | } 31 | return nil 32 | 33 | }, nil, "yum", "list", "updates", "-q") 34 | if err == util.ErrPath { 35 | return nil, nil 36 | } else if err != nil { 37 | return nil, err 38 | } 39 | Add(&md, "linux.updates.count", regular_c, opentsdb.TagSet{"type": "non-kernel"}, metadata.Gauge, metadata.Count, "") 40 | Add(&md, "linux.updates.count", kernel_c, opentsdb.TagSet{"type": "kernel"}, metadata.Gauge, metadata.Count, "") 41 | return md, nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/scollector/conf/conf_darwin.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type ProcessParams struct{} 4 | 5 | type ServiceParams struct { 6 | Name string 7 | WatchProc bool 8 | } 9 | 10 | func (c *Conf) InitializeSWbemServices() {} 11 | -------------------------------------------------------------------------------- /cmd/scollector/conf/conf_linux.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type ProcessParams struct { 4 | Command string 5 | Name string 6 | Args string 7 | IncludeCount bool 8 | } 9 | 10 | type ServiceParams struct { 11 | Name string 12 | WatchProc bool 13 | } 14 | 15 | func (c *Conf) InitializeSWbemServices() {} 16 | -------------------------------------------------------------------------------- /cmd/scollector/conf/conf_windows.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "bosun.org/slog" 5 | "github.com/StackExchange/wmi" 6 | ) 7 | 8 | type ProcessParams struct { 9 | Name string 10 | } 11 | 12 | type ServiceParams struct { 13 | Name string 14 | WatchProc bool 15 | } 16 | 17 | func (c *Conf) InitializeSWbemServices() { 18 | slog.Infof("Initializing SWbemServices") 19 | s, err := wmi.InitializeSWbemServices(wmi.DefaultClient) 20 | if err != nil { 21 | slog.Fatal(err) 22 | } 23 | wmi.DefaultClient.SWbemServicesClient = s 24 | } 25 | -------------------------------------------------------------------------------- /cmd/scollector/debug: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/cmd/scollector/debug -------------------------------------------------------------------------------- /cmd/scollector/log_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!nacl,!plan9 2 | 3 | package main 4 | 5 | import ( 6 | "bosun.org/_version" 7 | "bosun.org/slog" 8 | ) 9 | 10 | func init() { 11 | err := slog.SetSyslog("scollector") 12 | if err != nil { 13 | slog.Error(err) 14 | } 15 | slog.Infof("starting %s", version.GetVersionInfo("scollector")) 16 | } 17 | -------------------------------------------------------------------------------- /cmd/snmpTester/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | 10 | "bosun.org/cmd/scollector/collectors" 11 | "bosun.org/cmd/scollector/conf" 12 | "github.com/BurntSushi/toml" 13 | ) 14 | 15 | var devMode = flag.Bool("dev", false, "Dev mode. Use html from file-system instead of embedded copy.") 16 | 17 | // to embed static again: go:generate esc -modtime 0 -o=static.go -prefix=static static 18 | 19 | func main() { 20 | flag.Parse() 21 | fs := FS(*devMode) 22 | http.Handle("/", http.FileServer(fs)) 23 | http.HandleFunc("/test", TestMib) 24 | http.HandleFunc("/toml", Toml) 25 | http.ListenAndServe(":8888", nil) 26 | } 27 | 28 | func TestMib(w http.ResponseWriter, r *http.Request) { 29 | buf, err := ioutil.ReadAll(r.Body) 30 | if err != nil { 31 | http.Error(w, err.Error(), 500) 32 | return 33 | } 34 | mib := conf.MIB{} 35 | err = json.Unmarshal(buf, &mib) 36 | if err != nil { 37 | http.Error(w, err.Error(), 500) 38 | return 39 | } 40 | snmp := conf.SNMP{} 41 | err = json.Unmarshal(buf, &snmp) 42 | if err != nil { 43 | http.Error(w, err.Error(), 500) 44 | return 45 | } 46 | 47 | md, err := collectors.GenericSnmp(snmp, mib) 48 | if err != nil { 49 | log.Println(err) 50 | http.Error(w, err.Error(), 500) 51 | return 52 | } 53 | 54 | mdJson, err := json.MarshalIndent(md, "", " ") 55 | if err != nil { 56 | log.Println(err) 57 | http.Error(w, err.Error(), 500) 58 | return 59 | } 60 | w.Write(mdJson) 61 | } 62 | 63 | func Toml(w http.ResponseWriter, r *http.Request) { 64 | buf, err := ioutil.ReadAll(r.Body) 65 | if err != nil { 66 | http.Error(w, err.Error(), 500) 67 | return 68 | } 69 | mib := conf.MIB{} 70 | err = json.Unmarshal(buf, &mib) 71 | if err != nil { 72 | http.Error(w, err.Error(), 500) 73 | return 74 | } 75 | meta := &struct{ Name string }{} 76 | err = json.Unmarshal(buf, meta) 77 | if err != nil { 78 | http.Error(w, err.Error(), 500) 79 | return 80 | } 81 | 82 | toToml := struct { 83 | MIBs map[string]conf.MIB 84 | }{MIBs: map[string]conf.MIB{meta.Name: mib}} 85 | 86 | toml.NewEncoder(w).Encode(toToml) 87 | } 88 | -------------------------------------------------------------------------------- /cmd/tsdbrelay/denormalize/denormalization.go: -------------------------------------------------------------------------------- 1 | package denormalize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | "bosun.org/opentsdb" 9 | ) 10 | 11 | type DenormalizationRule struct { 12 | Metric string 13 | TagNames []string 14 | } 15 | 16 | func (d *DenormalizationRule) String() string { 17 | inputTags, outputTags := "", "" 18 | val := 'a' 19 | for i, tagk := range d.TagNames { 20 | if i != 0 { 21 | inputTags += "," 22 | outputTags += "." 23 | } 24 | inputTags += fmt.Sprintf("%s=%s", tagk, string(val)) 25 | outputTags += fmt.Sprintf("%s", string(val)) 26 | val++ 27 | } 28 | return fmt.Sprintf("%s{%s} -> __%s.%s", d.Metric, inputTags, outputTags, d.Metric) 29 | } 30 | 31 | func ParseDenormalizationRules(config string) (map[string]*DenormalizationRule, error) { 32 | m := make(map[string]*DenormalizationRule) 33 | rules := strings.Split(config, ",") 34 | for _, r := range rules { 35 | parts := strings.Split(r, "__") 36 | if len(parts) < 2 { 37 | return nil, fmt.Errorf("Denormalization rules must have at least one tag name specified.") 38 | } 39 | rule := &DenormalizationRule{Metric: parts[0]} 40 | rule.TagNames = append(rule.TagNames, parts[1:]...) 41 | log.Println("Denormalizing", rule) 42 | m[rule.Metric] = rule 43 | } 44 | return m, nil 45 | } 46 | 47 | func (d *DenormalizationRule) Translate(dp *opentsdb.DataPoint) error { 48 | tagString := "__" 49 | for i, tagName := range d.TagNames { 50 | val, ok := dp.Tags[tagName] 51 | if !ok { 52 | return fmt.Errorf("tag %s not present in data point for %s.", tagName, dp.Metric) 53 | } 54 | if i > 0 { 55 | tagString += "." 56 | } 57 | tagString += val 58 | } 59 | dp.Metric = tagString + "." + dp.Metric 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /cmd/tsdbrelay/denormalize/denormalization_test.go: -------------------------------------------------------------------------------- 1 | package denormalize 2 | 3 | import ( 4 | "testing" 5 | 6 | "bosun.org/opentsdb" 7 | ) 8 | 9 | func TestSimpleRewrite(t *testing.T) { 10 | rule := &DenormalizationRule{ 11 | Metric: "a.b.c", 12 | TagNames: []string{"host"}, 13 | } 14 | tags := opentsdb.TagSet{"host": "foo-bar", "baz": "qwerty"} 15 | dp := &opentsdb.DataPoint{ 16 | Metric: "a.b.c", 17 | Timestamp: 42, 18 | Value: 3, 19 | Tags: tags.Copy(), 20 | } 21 | err := rule.Translate(dp) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | expectedName := "__foo-bar.a.b.c" 26 | if dp.Metric != expectedName { 27 | t.Errorf("metric name %s is not `%s`", dp.Metric, expectedName) 28 | } 29 | if dp.Timestamp != 42 { 30 | t.Errorf("new metric timestamp does not match. %d != 42", dp.Timestamp) 31 | } 32 | if dp.Value != 3 { 33 | t.Errorf("new metric value does not match. %d != 3", dp.Value) 34 | } 35 | if !dp.Tags.Equal(tags) { 36 | t.Errorf("new metric tags do not match. %v != %v", dp.Tags, tags) 37 | } 38 | } 39 | 40 | func TestMultipleTags(t *testing.T) { 41 | rule := &DenormalizationRule{ 42 | Metric: "a.b.c", 43 | TagNames: []string{"host", "interface"}, 44 | } 45 | dp := &opentsdb.DataPoint{ 46 | Metric: "a.b.c", 47 | Tags: opentsdb.TagSet{"host": "foo-bar", "interface": "eth0"}, 48 | } 49 | err := rule.Translate(dp) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | expectedName := "__foo-bar.eth0.a.b.c" 54 | if dp.Metric != expectedName { 55 | t.Errorf("metric name %s is not `%s`", dp.Metric, expectedName) 56 | } 57 | } 58 | 59 | func TestRewrite_TagNotPresent(t *testing.T) { 60 | rule := &DenormalizationRule{ 61 | Metric: "a.b.c", 62 | TagNames: []string{"host"}, 63 | } 64 | // Denormalization rule specified host, but data point has no host. 65 | // Return error on translate and don't send anything downstream. 66 | dp := &opentsdb.DataPoint{ 67 | Metric: "a.b.c", 68 | Timestamp: 42, 69 | Value: 3, 70 | Tags: opentsdb.TagSet{"baz": "qwerty"}, 71 | } 72 | err := rule.Translate(dp) 73 | if err == nil { 74 | t.Fatal("Expected error but got none.") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /collect/eventListener.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "bosun.org/opentsdb" 11 | "bosun.org/slog" 12 | "github.com/garyburd/redigo/redis" 13 | ) 14 | 15 | func HandleCounterPut(server string, database int) http.HandlerFunc { 16 | 17 | pool := newRedisPool(server, database) 18 | return func(w http.ResponseWriter, r *http.Request) { 19 | gReader, err := gzip.NewReader(r.Body) 20 | if err != nil { 21 | http.Error(w, err.Error(), 500) 22 | return 23 | } 24 | decoder := json.NewDecoder(gReader) 25 | dps := []*opentsdb.DataPoint{} 26 | err = decoder.Decode(&dps) 27 | if err != nil { 28 | http.Error(w, err.Error(), 500) 29 | return 30 | } 31 | conn := pool.Get() 32 | defer conn.Close() 33 | for _, dp := range dps { 34 | mts := fmt.Sprintf("%s%s", dp.Metric, dp.Tags) 35 | var i int64 36 | switch v := dp.Value.(type) { 37 | case int: 38 | i = int64(v) 39 | case int64: 40 | i = v 41 | case float64: 42 | i = int64(v) 43 | default: 44 | http.Error(w, "Values must be integers.", 400) 45 | return 46 | } 47 | if _, err = conn.Do("HINCRBY", RedisCountersKey, mts, i); err != nil { 48 | slog.Errorf("Error incrementing counter %s by %d. %s", mts, i, err) 49 | http.Error(w, err.Error(), 500) 50 | return 51 | } 52 | } 53 | } 54 | } 55 | 56 | const RedisCountersKey = "scollectorCounters" 57 | 58 | func newRedisPool(server string, database int) *redis.Pool { 59 | return &redis.Pool{ 60 | MaxIdle: 10, 61 | MaxActive: 10, 62 | Wait: true, 63 | IdleTimeout: 240 * time.Second, 64 | Dial: func() (redis.Conn, error) { 65 | c, err := redis.Dial("tcp", server, redis.DialDatabase(database)) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return c, err 70 | }, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 AS bosun_builder 2 | 3 | WORKDIR /bosun 4 | COPY . /bosun 5 | 6 | RUN make bosun scollector tsdbrelay 7 | 8 | FROM alpine:latest 9 | 10 | ARG PACKAGES="ca-certificates rsyslog bash libc6-compat curl libgd libpng libjpeg libwebp libjpeg-turbo cairo pango lua supervisor asciidoctor" 11 | 12 | ARG DOCKER_ROOT="docker" 13 | 14 | ENV DATA_DIR=/data 15 | 16 | ENV TERM xterm 17 | ENV TSDBRELAY_OPTS -b localhost:8070 -t opentsdb:4242 -l 0.0.0.0:5252 -redis localhost:9565 18 | 19 | # Install dependencies 20 | RUN apk --update add apk-tools \ 21 | && apk add ${PACKAGES} 22 | 23 | # Copy Bosun from the build image 24 | WORKDIR /bosun 25 | RUN mkdir /scollector /tsdbrelay 26 | COPY --from=bosun_builder /bosun/bosun /bosun 27 | COPY --from=bosun_builder /bosun/scollector /scollector 28 | COPY --from=bosun_builder /bosun/tsdbrelay /tsdbrelay 29 | 30 | # Copy Bosun config 31 | COPY ${DOCKER_ROOT}/data/bosunrules.conf ${DATA_DIR}/ 32 | COPY ${DOCKER_ROOT}/data/bosun.toml ${DATA_DIR}/ 33 | COPY ${DOCKER_ROOT}/data/scollector.toml ${DATA_DIR}/ 34 | 35 | # Copy supervisor config 36 | COPY ${DOCKER_ROOT}/data/supervisord.conf ${DATA_DIR}/ 37 | 38 | EXPOSE 8070 5252 9565 39 | VOLUME ["${DATA_DIR}"] 40 | CMD ["sh", "-c", "/usr/bin/supervisord -c ${DATA_DIR}/supervisord.conf"] 41 | -------------------------------------------------------------------------------- /docker/data/bosunrules.conf: -------------------------------------------------------------------------------- 1 | #This file holds the Bosun alert rules, macros, notifications, lookups, and templates. It should be edited from the Rule Editor page. -------------------------------------------------------------------------------- /docker/data/scollector.toml: -------------------------------------------------------------------------------- 1 | #Direct to Bosun 2 | #Host = "localhost:8070" 3 | 4 | #Send to tsdbrelay. Use TSDBRELAY_OPTS environment variable for forwarding options 5 | Host = "localhost:5252" 6 | 7 | BatchSize = 5000 8 | 9 | #Monitoring of hbase inside the container 10 | HadoopHost = "opentsdb:16010" 11 | 12 | #Redis or ledis based external counters. See https://godoc.org/bosun.org/cmd/tsdbrelay 13 | [[RedisCounters]] 14 | Server = "redis:6379" 15 | -------------------------------------------------------------------------------- /docker/data/supervisord-opentsdb.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisord.log 5 | pidfile=/data/supervisord.pid 6 | 7 | [program:hbase] 8 | command=/hbase/start_hbase.sh 9 | priority=10 10 | redirect_stderr=true 11 | stdout_logfile=/var/log/%(program_name)s.log 12 | 13 | [program:opentsdb] 14 | command=/tsdb/start_opentsdb.sh 15 | priority=15 16 | redirect_stderr=true 17 | stdout_logfile=/var/log/%(program_name)s.log 18 | -------------------------------------------------------------------------------- /docker/data/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisord.log 5 | pidfile=/data/supervisord.pid 6 | 7 | [program:bosun] 8 | command=/bosun/bosun -c /data/bosun.toml 9 | priority=20 10 | redirect_stderr=true 11 | stdout_logfile=/dev/fd/1 ; redirects the process's stdout to supervisor's so that we can see it in the shell 12 | stdout_logfile_maxbytes=0 13 | 14 | [program:tsdbrelay] 15 | command=/tsdbrelay/tsdbrelay %(ENV_TSDBRELAY_OPTS)s 16 | priority=100 17 | redirect_stderr=true 18 | stdout_logfile=/dev/fd/1 ; redirects the process's stdout to supervisor's so that we can see it in the shell 19 | stdout_logfile_maxbytes=0 20 | 21 | [program:scollector] 22 | command=/scollector/scollector -conf %(ENV_DATA_DIR)s/scollector.toml 23 | priority=200 24 | redirect_stderr=true 25 | stdout_logfile=/dev/fd/1 ; redirects the process's stdout to supervisor's so that we can see it in the shell 26 | stdout_logfile_maxbytes=0 27 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | redis: 4 | image: redis:6 5 | container_name: redis 6 | ports: 7 | - "6379:6379" 8 | opentsdb: 9 | build: 10 | context: .. 11 | dockerfile: docker/opentsdb.Dockerfile 12 | container_name: opentsdb 13 | restart: on-failure 14 | ports: 15 | - "4242:4242" 16 | - "16010:16010" 17 | - "16030:16030" 18 | bosun: 19 | build: 20 | context: .. 21 | dockerfile: docker/Dockerfile 22 | depends_on: 23 | - redis 24 | - opentsdb 25 | container_name: bosun 26 | restart: always 27 | ports: 28 | - "8070:8070" 29 | -------------------------------------------------------------------------------- /docker/hbase-site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hbase.tmp.dir 6 | file:///data/hbase 7 | 8 | 9 | -------------------------------------------------------------------------------- /docker/start_hbase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #export JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/jre 3 | 4 | echo "127.0.0.1 localhost $(hostname)" > /etc/hosts 5 | trap "echo stopping hbase; ${HBASE_HOME}/bin/hbase master stop>>/var/log/hbase-stop.log 2>&1; exit" HUP INT TERM EXIT 6 | echo "starting hbase" 7 | ${HBASE_HOME}/bin/hbase master start >> /var/log/hbase-start.log 2>&1 & 8 | while true 9 | do 10 | sleep 1 11 | done 12 | -------------------------------------------------------------------------------- /docker/tsdb/create_tsdb_tables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export COMPRESSION="NONE" 4 | 5 | ${TSDB_DIR}/src/create_table.sh 6 | if [ $? -ne 0 ]; then 7 | echo "Opentsdb /src/create_table.sh failed" 8 | exit 1 9 | else 10 | touch ${TSDB_DIR}/opentsdb_tables_created.txt 11 | fi 12 | -------------------------------------------------------------------------------- /docker/tsdb/opentsdb.conf: -------------------------------------------------------------------------------- 1 | tsd.core.socket.timeout = 60 2 | tsd.core.auto_create_metrics=true 3 | tsd.http.request.enable_chunked=true 4 | tsd.http.request.max_chunk=33554432 5 | tsd.storage.fix_duplicates=true 6 | -------------------------------------------------------------------------------- /docker/tsdb/start_opentsdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Sleeping for 30 seconds to give HBase time to warm up" 3 | sleep 30 4 | 5 | if [ ! -e ${TSDB_DIR}/opentsdb_tables_created.txt ]; then 6 | echo "creating tsdb tables" 7 | bash ${TSDB_DIR}/create_tsdb_tables.sh 8 | echo "created tsdb tables" 9 | fi 10 | 11 | echo "starting opentsdb" 12 | ${TSDB_DIR}/build/tsdb tsd --port=4242 --staticroot=${TSDB_DIR}/build/staticroot --cachedir=/tmp --auto-metric --config=${TSDB_DIR}/opentsdb.conf 13 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore docs files 2 | _gh_pages 3 | _site 4 | .ruby-version 5 | 6 | # Numerous always-ignore extensions 7 | *.diff 8 | *.err 9 | *.orig 10 | *.log 11 | *.rej 12 | *.swo 13 | *.swp 14 | *.zip 15 | *.vi 16 | *~ 17 | 18 | # OS or Editor folders 19 | .DS_Store 20 | ._* 21 | Thumbs.db 22 | .cache 23 | .project 24 | .settings 25 | .tmproj 26 | *.esproj 27 | nbproject 28 | *.sublime-project 29 | *.sublime-workspace 30 | .idea 31 | 32 | # Komodo 33 | *.komodoproject 34 | .komodotools 35 | 36 | # grunt-html-validation 37 | validation-status.json 38 | validation-report.json 39 | 40 | # Folders to ignore 41 | node_modules 42 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: "404: Page not found" 4 | --- 5 | 6 |
7 |

404: Page not found

8 |

Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. Head back home to try finding it again.

9 |
10 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | bosun.org 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | These are the official docs for bosun, and the source code for https://bosun.org. They are in the main bosun repo so that documentation can be included with code changes in the same pr. 2 | 3 | Once changes have been committed to master they will show up on the https://bosun.org website as soon as github pages picks them up. 4 | 5 | The best way to develop locally is to use docker. This can be run using the `docker-compose.yml` inside the `docker` 6 | directory. This is because it includes some github pages specific libraries that are needed for everything to render 7 | correctly. 8 | 9 | Alternatively you can use jekyll locally (however more gems are needed to render correctly): 10 | 11 | ``` 12 | gem install jekyll 13 | gem install jekyll-redirect-from 14 | jekyll server 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/_config.yml -------------------------------------------------------------------------------- /docs/_jekyll.config.yml: -------------------------------------------------------------------------------- 1 | #markdown: redcarpet 2 | #highlighter: pygments 3 | 4 | redcarpet: 5 | extensions: ["no_intra_emphasis", "fenced_code_blocks", "autolink", "strikethrough", "superscript"] 6 | repository: bosun-monitor/bosun 7 | 8 | markdown_ext: markdown,mkd,mkdn,md 9 | textile_ext: textile 10 | 11 | # gem install github-pages 12 | # gem install jekyll-github-metadata jekyll-mentions jekyll-redirect-from jekyll-sitemap jemoji 13 | #gems: 14 | #- jekyll-github-metadata 15 | #- jekyll-mentions 16 | #- jekyll-redirect-from 17 | #- jekyll-sitemap 18 | #- jemoji 19 | -------------------------------------------------------------------------------- /docs/_layouts/goimport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Nothing to see here; move along. 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |
7 | {% if page.title != "Home" %} 8 |

{{ page.title }}

9 | {% endif %} 10 | {{ content }} 11 |
12 |
13 | -------------------------------------------------------------------------------- /docs/cmd/backfill/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/backfill 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/cache/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/cache 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/conf/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/conf 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/conf/parse/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/conf/parse 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/database/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/database 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/database/test/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/database/test 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/expr/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/expr 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/expr/parse/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/expr/parse 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/sched/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/sched 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/search/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/search 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/bosun/web/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/bosun/web 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/scollector/collectors/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/scollector/collectors 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/scollector/conf/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/scollector/conf 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/scollector/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/scollector 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/silence/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/silence 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/snmpTester/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/snmpTester 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/tsdbrelay/denormalize/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/tsdbrelay/denormalize 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/tsdbrelay/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/tsdbrelay 4 | --- 5 | -------------------------------------------------------------------------------- /docs/cmd/tsdbrelay/integrationTest/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: cmd/tsdbrelay/integrationTest 4 | --- 5 | -------------------------------------------------------------------------------- /docs/collect/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: collect 4 | --- 5 | -------------------------------------------------------------------------------- /docs/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker run --rm -v "$PWD:/src" -p 4000:4000 -p 35729:35729 markkimsal/jekyll-plus -------------------------------------------------------------------------------- /docs/downloads.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Download Bosun 4 | --- 5 | 6 |
7 |
8 | 9 | {% if site.github != null %} 10 | 11 | {% assign releases = site.github.releases | where:"draft",false %} 12 | {% assign release = releases[0] %} 13 | {% assign relname = release.tag_name %} 14 |

Latest release: {{relname}} Published {{release.created_at | date_to_string}} 15 |

{{ release.body | markdownify }}

16 |

Binaries

17 | 18 |

Binaries are provided below. All web assets are already bundled. Source instructions provided for developers.

19 | 29 | 30 |

View upcoming features on Github. 31 | {% else %} 32 |

View latest release on Github

33 | {% endif %} 34 |

From Source

35 | $ go get bosun.org/cmd/bosun 36 |
37 |
-------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/graphite/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: graphite 4 | --- 5 | -------------------------------------------------------------------------------- /docs/metadata/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: metadata 4 | --- 5 | -------------------------------------------------------------------------------- /docs/models/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: models 4 | --- 5 | -------------------------------------------------------------------------------- /docs/opentsdb/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: opentsdb 4 | --- 5 | -------------------------------------------------------------------------------- /docs/public/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/agent.png -------------------------------------------------------------------------------- /docs/public/anom_route_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/anom_route_notification.png -------------------------------------------------------------------------------- /docs/public/anom_traffic_vol_graphite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/anom_traffic_vol_graphite.png -------------------------------------------------------------------------------- /docs/public/app-store.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /docs/public/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/arch.png -------------------------------------------------------------------------------- /docs/public/bosun-logo-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/public/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /docs/public/cpu_idle_graph_graphite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/cpu_idle_graph_graphite.png -------------------------------------------------------------------------------- /docs/public/createToken.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/createToken.jpg -------------------------------------------------------------------------------- /docs/public/createdToken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/createdToken.png -------------------------------------------------------------------------------- /docs/public/database.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/disk_forecast_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/disk_forecast_notification.png -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /docs/public/hour-glass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /docs/public/hw_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/hw_notification.png -------------------------------------------------------------------------------- /docs/public/inbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/netbackup_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/netbackup_notification.png -------------------------------------------------------------------------------- /docs/public/rule_editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/rule_editor.jpg -------------------------------------------------------------------------------- /docs/public/rule_editor.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/rule_editor.xcf -------------------------------------------------------------------------------- /docs/public/sound-mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/ss_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_dashboard.png -------------------------------------------------------------------------------- /docs/public/ss_expr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_expr.png -------------------------------------------------------------------------------- /docs/public/ss_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_graph.png -------------------------------------------------------------------------------- /docs/public/ss_host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_host.png -------------------------------------------------------------------------------- /docs/public/ss_rule_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_rule_results.png -------------------------------------------------------------------------------- /docs/public/ss_rule_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_rule_template.png -------------------------------------------------------------------------------- /docs/public/ss_rule_timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/ss_rule_timeline.png -------------------------------------------------------------------------------- /docs/public/stackexchange-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/stackexchange-logo.png -------------------------------------------------------------------------------- /docs/public/t_ill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/t_ill.jpg -------------------------------------------------------------------------------- /docs/public/t_stepthrough_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/t_stepthrough_1.jpg -------------------------------------------------------------------------------- /docs/public/t_stepthrough_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/t_stepthrough_2.jpg -------------------------------------------------------------------------------- /docs/public/t_stepthrough_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/t_stepthrough_3.jpg -------------------------------------------------------------------------------- /docs/public/tcp_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/tcp_notification.png -------------------------------------------------------------------------------- /docs/public/timeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/timeline.jpg -------------------------------------------------------------------------------- /docs/public/tux-large-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/tux-large-bw.png -------------------------------------------------------------------------------- /docs/public/vimeo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bosun-monitor/bosun/b8d3e981f37d353292afc46d88bc2a174ff593dc/docs/public/vimeo-logo.png -------------------------------------------------------------------------------- /docs/public/windows-store.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /docs/scollector/javascripts/scale.fix.js: -------------------------------------------------------------------------------- 1 | var metas = document.getElementsByTagName('meta'); 2 | var i; 3 | if (navigator.userAgent.match(/iPhone/i)) { 4 | for (i=0; iMany bosun devs and users are usually availible on our slack channel. If you have any questions or issues this is the best place to 7 | get help. Please fill out this form to automatically receive an invitation to join us:

8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 34 | -------------------------------------------------------------------------------- /docs/slog/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: slog 4 | --- 5 | -------------------------------------------------------------------------------- /docs/snmp/asn1/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: snmp/asn1 4 | --- 5 | -------------------------------------------------------------------------------- /docs/snmp/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: snmp 4 | --- 5 | -------------------------------------------------------------------------------- /docs/snmp/mib/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: snmp/mib 4 | --- 5 | -------------------------------------------------------------------------------- /docs/util/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: util 4 | --- 5 | -------------------------------------------------------------------------------- /docs/version/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: version 4 | --- 5 | -------------------------------------------------------------------------------- /docs/vsphere/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: goimport 3 | path: vsphere 4 | --- 5 | -------------------------------------------------------------------------------- /graphite/graphite_test.go: -------------------------------------------------------------------------------- 1 | package graphite 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | // RoundTripFunc . 14 | type RoundTripFunc func(req *http.Request) *http.Response 15 | 16 | // RoundTrip . 17 | func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 18 | return f(req), nil 19 | } 20 | 21 | //NewTestClient returns *http.Client with Transport replaced to avoid making real calls 22 | func NewTestClient(fn RoundTripFunc) *http.Client { 23 | return &http.Client{ 24 | Transport: RoundTripFunc(fn), 25 | } 26 | } 27 | 28 | func TestUrlParsePanic(t *testing.T) { 29 | // url.Parse has unexpected behavior when not specifying a scheme 30 | // This would cause a panic in Query when using ip addresses with no scheme 31 | // e.g. 127.0.0.1:8080 32 | 33 | tests := []struct { 34 | host string 35 | URL url.URL 36 | }{ 37 | {"localhost:8080", url.URL{Scheme: "http", Host: "localhost:8080", Path: "/render/", RawQuery: "format=json"}}, 38 | {"127.0.0.1:8080", url.URL{Scheme: "http", Host: "127.0.0.1:8080", Path: "/render/", RawQuery: "format=json"}}, 39 | {"https://graphite.skyscanner.net:8080", url.URL{Scheme: "https", Host: "graphite.skyscanner.net:8080", Path: "/render/", RawQuery: "format=json"}}} 40 | 41 | r := Request{} 42 | stockResponse := http.Response{ 43 | StatusCode: 200, 44 | Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)), 45 | Header: make(http.Header), 46 | } 47 | 48 | for _, test := range tests { 49 | DefaultClient = NewTestClient(func(req *http.Request) *http.Response { 50 | assert.Equal(t, test.URL, *req.URL) 51 | return &stockResponse 52 | 53 | }) 54 | r.Query(test.host, nil) 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /host/manager_test.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func validateManager(m Manager, expectedHostname string, t *testing.T) { 9 | expectedHostname = strings.ToLower(expectedHostname) 10 | 11 | if m.GetHostName() != expectedHostname { 12 | t.Errorf("Expected hostname to be '%s' but it was '%s'", expectedHostname, m.GetHostName()) 13 | } else if m.GetHost().GetName() != expectedHostname { 14 | t.Errorf("Expected hostname to be '%s' but it was '%s'", expectedHostname, m.GetHostName()) 15 | } else if m.GetNameProcessor() == nil { 16 | t.Errorf("Expected managed host to have a name processor") 17 | } 18 | } 19 | 20 | func TestNewManager(t *testing.T) { 21 | // we should save fqdn for NewManager since os.Hostname will return /proc/sys/kernel/hostname and it can be fqdn 22 | // underhood NewManager will use same call, but if we don't save fqdn we are trying to split that name 23 | // In that can this test will fail anyway 24 | testHostnames := []struct { 25 | hostname string 26 | expected string 27 | preserveFullHostName bool 28 | }{ 29 | {"test1", "test1", true}, 30 | {"test1", "test1", false}, 31 | {"test1.domain.com", "test1.domain.com", true}, 32 | {"test1.domain.com", "test1", false}, 33 | {"test-01.domain.com", "test-01", false}, 34 | {"test-01.domain.com", "test-01.domain.com", true}, 35 | {"test-103.subdomain1.subdomain2.domain.com", "test-103", false}, 36 | {"test-103.subdomain1.subdomain2.domain.com", "test-103.subdomain1.subdomain2.domain.com", true}, 37 | } 38 | for _, params := range testHostnames { 39 | hostname = func() (string, error) { return params.hostname, nil } 40 | m, err := NewManager(params.preserveFullHostName) 41 | if err != nil { 42 | t.Errorf("Error while create new manager for %s: %v", params.hostname, err) 43 | } 44 | 45 | validateManager(m, params.expected, t) 46 | } 47 | 48 | } 49 | 50 | func TestNewManagerForHostname(t *testing.T) { 51 | expectedHostname := "JiMMYs-PC" 52 | m, err := NewManagerForHostname(expectedHostname, false) 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | 57 | validateManager(m, expectedHostname, t) 58 | } 59 | -------------------------------------------------------------------------------- /host/name.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "bosun.org/name" 10 | ) 11 | 12 | const hostRegexPattern = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$` 13 | 14 | type hostNameFormatConfig struct { 15 | useFullName bool 16 | validators []name.Validator 17 | } 18 | 19 | // NewHostNameProcessor constructs a new name.Processor for host names 20 | func NewHostNameProcessor(useFullName bool) (name.Processor, error) { 21 | lenValidator := name.NewLengthValidator(1, 255) 22 | regexValidator, err := name.NewRegexpValidator(hostRegexPattern) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | result := &hostNameFormatConfig{ 28 | useFullName: useFullName, 29 | validators: []name.Validator{lenValidator, regexValidator}, 30 | } 31 | 32 | return result, nil 33 | } 34 | 35 | // IsValid returns true if the provided name is valid according to https://tools.ietf.org/html/rfc1123 36 | func (c *hostNameFormatConfig) IsValid(name string) bool { 37 | for _, validator := range c.validators { 38 | if !validator.IsValid(name) { 39 | return false 40 | } 41 | } 42 | 43 | return true 44 | } 45 | 46 | // FormatName takes a host name and formats it. 47 | // 48 | // If the name is an IP address then it's simply returned. 49 | // 50 | // If `useFullName == false` then the name is truncated at the first '.'. If there is no '.' then the full name is 51 | // returned. 52 | // 53 | // The resulting names will always be in lowercase format. 54 | func (c *hostNameFormatConfig) FormatName(name string) (string, error) { 55 | if !c.useFullName { 56 | //only split if string is not an IP address 57 | ip := net.ParseIP(name) 58 | if ip == nil { 59 | name = strings.SplitN(name, ".", 2)[0] 60 | } 61 | } 62 | 63 | if !c.IsValid(name) { 64 | return "", errors.New(fmt.Sprintf("Invalid name of '%s'", name)) 65 | } 66 | return strings.ToLower(name), nil 67 | } 68 | -------------------------------------------------------------------------------- /host/name_test.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import "testing" 4 | 5 | func TestFormatName_Short(t *testing.T) { 6 | testCases := make([][]string, 3) 7 | testCases[0] = []string{"freddy", "freddy"} 8 | testCases[1] = []string{"freddy.somewhere.com", "freddy"} 9 | testCases[2] = []string{"192.168.17.6", "192.168.17.6"} 10 | 11 | np, err := NewHostNameProcessor(false) 12 | if err != nil { 13 | t.Error(err) 14 | return 15 | } 16 | 17 | for i, tc := range testCases { 18 | name, err := np.FormatName(tc[0]) 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | 23 | if name != tc[1] { 24 | t.Errorf("Iteration %d: '%s' != '%s'", i, tc[1], name) 25 | } 26 | } 27 | } 28 | 29 | func TestFormatName_Full(t *testing.T) { 30 | testCases := make([]string, 3) 31 | testCases[0] = "freddy" 32 | testCases[1] = "freddy.somewhere.com" 33 | testCases[2] = "192.168.17.6" 34 | 35 | np, err := NewHostNameProcessor(true) 36 | if err != nil { 37 | t.Error(err) 38 | return 39 | } 40 | 41 | for i, tc := range testCases { 42 | name, err := np.FormatName(tc) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | 47 | if name != tc { 48 | t.Errorf("Iteration %d: '%s' != '%s'", i, tc, name) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /metadata/shared.go: -------------------------------------------------------------------------------- 1 | package metadata // import "bosun.org/metadata" 2 | 3 | type HWDiskMeta struct { 4 | Name string 5 | Media string 6 | Capacity string 7 | VendorId string 8 | ProductId string 9 | Serial string 10 | Part string 11 | NegotatiedSpeed string 12 | CapableSpeed string 13 | SectorSize string 14 | } 15 | 16 | type HWControllerMeta struct { 17 | Name string 18 | SlotId string 19 | State string 20 | FirmwareVersion string 21 | DriverVersion string 22 | } 23 | 24 | type HWPowerSupplyMeta struct { 25 | RatedInputWattage string 26 | RatedOutputWattage string 27 | } 28 | -------------------------------------------------------------------------------- /mk_rpm_fpmdir.bosun-silence.txt: -------------------------------------------------------------------------------- 1 | exec /usr/bin/silence cmd/silence/silence 2 | -------------------------------------------------------------------------------- /mk_rpm_fpmdir.scollector.txt: -------------------------------------------------------------------------------- 1 | # The binary itself: 2 | exec /opt/scollector/scollector cmd/scollector/scollector 3 | # Common utility functions shared for Python collectors: 4 | exec /opt/scollector/collectors/lib/utils.py plugins/utils.py 5 | # Needed by mysqlconf.py: 6 | exec /opt/scollector/collectors/etc/mysqlconf.py plugins/mysqlconf.py 7 | -------------------------------------------------------------------------------- /mk_rpm_fpmdir.tsdbrelay.txt: -------------------------------------------------------------------------------- 1 | exec /opt/tsdbrelay/tsdbrelay cmd/tsdbrelay/tsdbrelay 2 | -------------------------------------------------------------------------------- /models/alertKey.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "bosun.org/opentsdb" 8 | ) 9 | 10 | type AlertKey string 11 | 12 | func ParseAlertKey(a string) (ak AlertKey, err error) { 13 | ak = AlertKey(a) 14 | defer func() { 15 | e := recover() 16 | if e != nil { 17 | err = fmt.Errorf("%v", e) 18 | } 19 | }() 20 | ak.Group() 21 | return 22 | } 23 | 24 | func NewAlertKey(name string, group opentsdb.TagSet) AlertKey { 25 | return AlertKey(name + group.String()) 26 | } 27 | 28 | func (a AlertKey) Name() string { 29 | return strings.SplitN(string(a), "{", 2)[0] 30 | } 31 | 32 | // Group returns the tagset of this alert key. Will panic if a is not a valid 33 | // AlertKey. OpenTSDB tag validation errors are ignored. 34 | func (a AlertKey) Group() opentsdb.TagSet { 35 | sp := strings.SplitN(string(a), "{", 2) 36 | if len(sp) < 2 { 37 | panic(fmt.Errorf("invalid alert key %s", a)) 38 | } 39 | s := sp[1] 40 | s = s[:len(s)-1] 41 | if s == "" { 42 | return nil 43 | } 44 | g, err := opentsdb.ParseTags(s) 45 | if g == nil && err != nil { 46 | panic(err) 47 | } 48 | return g 49 | } 50 | 51 | type AlertKeys []AlertKey 52 | 53 | func (a AlertKeys) Len() int { return len(a) } 54 | func (a AlertKeys) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 55 | func (a AlertKeys) Less(i, j int) bool { return a[i] < a[j] } 56 | -------------------------------------------------------------------------------- /models/errors.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type AlertError struct { 8 | FirstTime, LastTime time.Time 9 | Count int 10 | Message string 11 | } 12 | -------------------------------------------------------------------------------- /models/silence.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "time" 7 | 8 | "bosun.org/opentsdb" 9 | "bosun.org/util" 10 | ) 11 | 12 | type Silence struct { 13 | Start, End time.Time 14 | Alert string 15 | Tags opentsdb.TagSet 16 | TagString string 17 | Forget bool 18 | User string 19 | Message string 20 | } 21 | 22 | func (s *Silence) Silenced(now time.Time, alert string, tags opentsdb.TagSet) bool { 23 | if !s.ActiveAt(now) { 24 | return false 25 | } 26 | return s.Matches(alert, tags) 27 | } 28 | 29 | func (s *Silence) ActiveAt(now time.Time) bool { 30 | if now.Before(s.Start) || now.After(s.End) { 31 | return false 32 | } 33 | return true 34 | } 35 | 36 | func (s *Silence) Matches(alert string, tags opentsdb.TagSet) bool { 37 | if s.Alert != "" && s.Alert != alert { 38 | return false 39 | } 40 | for k, pattern := range s.Tags { 41 | tagv, ok := tags[k] 42 | if !ok { 43 | return false 44 | } 45 | matched, _ := util.Match(pattern, tagv) 46 | if !matched { 47 | return false 48 | } 49 | } 50 | return true 51 | } 52 | 53 | func (s Silence) ID() string { 54 | h := sha1.New() 55 | fmt.Fprintf(h, "%s|%s|%s%s", s.Start, s.End, s.Alert, s.Tags) 56 | return fmt.Sprintf("%x", h.Sum(nil)) 57 | } 58 | -------------------------------------------------------------------------------- /name/basic.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import "errors" 4 | 5 | type basicValidationConfig struct { 6 | isEmptyValid bool 7 | isValidRuneCheck func(rune) bool 8 | } 9 | 10 | // NewBasicValidator constructs a RuneLevelValidator which can validate names for being empty or containing isolated 11 | // runes which fail a basic validation check 12 | func NewBasicValidator(isEmptyValid bool, isValidRuneCheck func(rune) bool) (RuneLevelValidator, error) { 13 | if isValidRuneCheck == nil { 14 | return nil, errors.New("no isValidRuneCheck provided") 15 | } 16 | 17 | result := &basicValidationConfig{isEmptyValid: isEmptyValid, isValidRuneCheck: isValidRuneCheck} 18 | return result, nil 19 | } 20 | 21 | // IsValid returns true if the given name is valid according the objects validation checks 22 | func (c *basicValidationConfig) IsValid(name string) bool { 23 | if len(name) == 0 { 24 | return c.isEmptyValid 25 | } 26 | 27 | for _, r := range name { 28 | if !c.isValidRuneCheck(r) { 29 | return false 30 | } 31 | } 32 | 33 | return true 34 | } 35 | 36 | // IsRuneValid returns true if the given rune is valid for use within a name 37 | func (c *basicValidationConfig) IsRuneValid(r rune) bool { 38 | return c.isValidRuneCheck(r) 39 | } 40 | -------------------------------------------------------------------------------- /name/basic_test.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import ( 4 | "testing" 5 | "unicode" 6 | ) 7 | 8 | func createBasicVaidator(t *testing.T) RuneLevelValidator { 9 | isValidTest := func(r rune) bool { 10 | return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' || r == '_' || r == '.' || r == '/' 11 | } 12 | validator, err := NewBasicValidator(false, isValidTest) 13 | if err != nil { 14 | t.Error(err) 15 | return nil 16 | } 17 | 18 | return validator 19 | } 20 | 21 | func TestBasicValidation(t *testing.T) { 22 | testCases := []struct { 23 | testString string 24 | expectPass bool 25 | }{ 26 | {"abc", true}, 27 | {"one.two.three", true}, 28 | {"1/2/3/4", true}, 29 | {"abc-123/456_xyz", true}, 30 | {"", false}, 31 | {" ", false}, 32 | {"abc$", false}, 33 | {"abc!xyz", false}, 34 | } 35 | 36 | validator := createBasicVaidator(t) 37 | 38 | for _, testCase := range testCases { 39 | if validator.IsValid(testCase.testString) != testCase.expectPass { 40 | t.Errorf("Expected IsValid test for '%s' to yeild '%t' not '%t'", 41 | testCase.testString, testCase.expectPass, !testCase.expectPass) 42 | } 43 | } 44 | } 45 | 46 | func TestRuneLevelValidation(t *testing.T) { 47 | testCases := []struct { 48 | testRune rune 49 | expectPass bool 50 | }{ 51 | {'a', true}, 52 | {'Z', true}, 53 | {'0', true}, 54 | {'9', true}, 55 | {'-', true}, 56 | {'_', true}, 57 | {'.', true}, 58 | {'/', true}, 59 | 60 | {'£', false}, 61 | {'$', false}, 62 | {'?', false}, 63 | } 64 | 65 | validator := createBasicVaidator(t) 66 | 67 | for _, testCase := range testCases { 68 | if validator.IsRuneValid(testCase.testRune) != testCase.expectPass { 69 | t.Errorf("Expected rune '%c' to be [valid=%t] but it was [valid=%t]", 70 | testCase.testRune, testCase.expectPass, !testCase.expectPass) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /name/length.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | type lengthValidationConfig struct { 4 | minLen uint 5 | maxLen uint 6 | } 7 | 8 | // NewLengthValidator constructs a Validator which can verify that names are of a correct length 9 | func NewLengthValidator(min uint, max uint) Validator { 10 | return &lengthValidationConfig{minLen: min, maxLen: max} 11 | } 12 | 13 | // IsValid returns true if the name if of an acceptable length 14 | func (c *lengthValidationConfig) IsValid(name string) bool { 15 | length := uint(len(name)) 16 | 17 | return length >= c.minLen && length <= c.maxLen 18 | } 19 | -------------------------------------------------------------------------------- /name/length_test.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import "testing" 4 | 5 | func TestLengthValidation(t *testing.T) { 6 | testCases := []struct { 7 | testString string 8 | expectPass bool 9 | }{ 10 | {"123", true}, 11 | {"123abc", true}, 12 | {"!@£$%^&", true}, 13 | {"0123456789", true}, 14 | {"", false}, 15 | {"1", false}, 16 | {"ab", false}, 17 | {"01234567890", false}, 18 | } 19 | 20 | validator := NewLengthValidator(3, 10) 21 | 22 | for _, testCase := range testCases { 23 | if validator.IsValid(testCase.testString) != testCase.expectPass { 24 | t.Errorf("Expected IsValid test for '%s' to yeild '%t' not '%t'", 25 | testCase.testString, testCase.expectPass, !testCase.expectPass) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /name/name.go: -------------------------------------------------------------------------------- 1 | // Package 'name' contains contracts and functionality to help with naming things. 2 | package name 3 | 4 | // Validator is an interface which declares operations associated with name validation. 5 | // 6 | // IsValid returns true if the provided name is valid. 7 | type Validator interface { 8 | IsValid(name string) bool 9 | } 10 | 11 | // RuneLevelValidator is Validator which can also validate isolated runes within a name. 12 | // 13 | // IsRuneValid returns true if the provided rune is a valid component of a name 14 | type RuneLevelValidator interface { 15 | Validator 16 | IsRuneValid(r rune) bool 17 | } 18 | 19 | // Formatter is an interface which defines operations for types which can format names. 20 | // 21 | // FormatName takes a name, formats it and then returns the result. 22 | type Formatter interface { 23 | FormatName(name string) (string, error) 24 | } 25 | 26 | // Processor is a Validator and Formatter. 27 | type Processor interface { 28 | Validator 29 | Formatter 30 | } 31 | 32 | // RuneLevelProcessor is a RuneLevelValidator and Formatter. 33 | type RuneLevelProcessor interface { 34 | RuneLevelValidator 35 | Formatter 36 | } 37 | -------------------------------------------------------------------------------- /name/regexp.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | ) 7 | 8 | type regexpValidationConfig struct { 9 | matcher *regexp.Regexp 10 | } 11 | 12 | // NewRegexpValidator constructs a new Validator which verifies that a name matches a specific regular expression 13 | func NewRegexpValidator(validPattern string) (Validator, error) { 14 | if len(validPattern) == 0 { 15 | return nil, errors.New("no validPattern provided") 16 | } 17 | 18 | matcher, err := regexp.Compile(validPattern) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | result := ®expValidationConfig{matcher: matcher} 24 | return result, nil 25 | } 26 | 27 | // IsValid returns true if the given name matches the Validators regular expression 28 | func (c *regexpValidationConfig) IsValid(name string) bool { 29 | return c.matcher.MatchString(name) 30 | } 31 | -------------------------------------------------------------------------------- /name/regexp_test.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | const hostnamePattern = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$` 10 | 11 | func generateString(length int) string { 12 | sb := strings.Builder{} 13 | for i := 0; i < length; i++ { 14 | sb.WriteString("a") 15 | } 16 | 17 | return sb.String() 18 | } 19 | 20 | func TestRegexpValidation(t *testing.T) { 21 | testCases := []struct { 22 | testString string 23 | expectPass bool 24 | }{ 25 | {"host", true}, 26 | {"host-name", true}, 27 | {"machine.domain.com", true}, 28 | {"abc123", true}, 29 | {"123.com", true}, 30 | {"192.168.0.12", true}, 31 | {generateString(63), true}, 32 | {fmt.Sprintf("%s.%s.%s.%s.%s", generateString(63), generateString(63), generateString(63), generateString(63), generateString(63)), true}, 33 | 34 | {"", false}, 35 | {" ", false}, 36 | {"-host", false}, 37 | {"host-", false}, 38 | {"host.", false}, 39 | {"host|name", false}, 40 | {"host name", false}, 41 | {generateString(64), false}, 42 | {"abc." + generateString(64), false}, 43 | } 44 | 45 | validator, err := NewRegexpValidator(hostnamePattern) 46 | if err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | 51 | for _, testCase := range testCases { 52 | if validator.IsValid(testCase.testString) != testCase.expectPass { 53 | t.Errorf("Expected IsValid test for '%s' to yeild '%t' not '%t'", 54 | testCase.testString, testCase.expectPass, !testCase.expectPass) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bosun", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "typescript": "^2.4.2" 9 | } 10 | }, 11 | "node_modules/typescript": { 12 | "version": "2.4.2", 13 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", 14 | "integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", 15 | "bin": { 16 | "tsc": "bin/tsc", 17 | "tsserver": "bin/tsserver" 18 | }, 19 | "engines": { 20 | "node": ">=4.2.0" 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "typescript": "^2.4.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /plugins/mysqlconf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | def get_user_password(sockfile): 4 | """Given the path of a socket file, returns a tuple (user, password).""" 5 | return ("root", file('/etc/mysql/root.pw').read().strip()) 6 | -------------------------------------------------------------------------------- /plugins/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # This file is part of tcollector. 3 | # Copyright (C) 2013 The tcollector Authors. 4 | # 5 | # This program is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or (at your 8 | # option) any later version. This program is distributed in the hope that it 9 | # will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 10 | # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 11 | # General Public License for more details. You should have received a copy 12 | # of the GNU Lesser General Public License along with this program. If not, 13 | # see . 14 | 15 | """Common utility functions shared for Python collectors""" 16 | 17 | import os 18 | import stat 19 | import pwd 20 | import errno 21 | import sys 22 | 23 | # If we're running as root and this user exists, we'll drop privileges. 24 | USER = "nobody" 25 | 26 | 27 | def drop_privileges(user=USER): 28 | """Drops privileges if running as root.""" 29 | try: 30 | ent = pwd.getpwnam(user) 31 | except KeyError: 32 | return 33 | 34 | if os.getuid() != 0: 35 | return 36 | 37 | os.setgid(ent.pw_gid) 38 | os.setuid(ent.pw_uid) 39 | 40 | 41 | def is_sockfile(path): 42 | """Returns whether or not the given path is a socket file.""" 43 | try: 44 | s = os.stat(path) 45 | except OSError, (no, e): 46 | if no == errno.ENOENT: 47 | return False 48 | err("warning: couldn't stat(%r): %s" % (path, e)) 49 | return None 50 | return s.st_mode & stat.S_IFSOCK == stat.S_IFSOCK 51 | 52 | 53 | def err(msg): 54 | print >> sys.stderr, msg 55 | 56 | 57 | def is_numeric(value): 58 | return isinstance(value, (int, long, float)) -------------------------------------------------------------------------------- /slog/README.md: -------------------------------------------------------------------------------- 1 | slog 2 | ==== 3 | 4 | Cross-platform logger for Go 5 | -------------------------------------------------------------------------------- /slog/slog_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!nacl,!plan9 2 | 3 | package slog 4 | 5 | import "log/syslog" 6 | 7 | // SetSyslog configures slog to use the system syslog daemon. 8 | func SetSyslog(tag string) error { 9 | w, err := syslog.New(syslog.LOG_LOCAL6, tag) 10 | if err != nil { 11 | return err 12 | } 13 | Set(&Syslog{W: w}) 14 | return nil 15 | } 16 | 17 | // Syslog logs to syslog. 18 | type Syslog struct { 19 | W *syslog.Writer 20 | } 21 | 22 | // Fatal logs a fatal message and calls os.Exit(1). 23 | func (s *Syslog) Fatal(v string) { 24 | s.W.Crit("crit: " + v) 25 | } 26 | 27 | // Error logs an error message. 28 | func (s *Syslog) Error(v string) { 29 | s.W.Err("error: " + v) 30 | } 31 | 32 | // Info logs an info message. 33 | func (s *Syslog) Info(v string) { 34 | // Mac OSX ignores levels info and debug by default, so use notice. 35 | s.W.Notice("info: " + v) 36 | } 37 | 38 | // Warning logs a warning message. 39 | func (s *Syslog) Warning(v string) { 40 | s.W.Warning("warning: " + v) 41 | } 42 | -------------------------------------------------------------------------------- /slog/slog_windows.go: -------------------------------------------------------------------------------- 1 | package slog 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sys/windows/svc/debug" 7 | ) 8 | 9 | type eventLog struct { 10 | l debug.Log 11 | id uint32 12 | } 13 | 14 | // Sets the logger to a Windows Event Log. Designed for use with the 15 | // code.google.com/p/winsvc/eventlog and code.google.com/p/winsvc/debug 16 | // packages. 17 | func SetEventLog(l debug.Log, eid uint32) { 18 | Set(&eventLog{l, eid}) 19 | } 20 | 21 | func (e *eventLog) Fatal(v string) { 22 | e.l.Error(e.id, fmt.Sprintf("fatal: %s", v)) 23 | } 24 | 25 | func (e *eventLog) Info(v string) { 26 | e.l.Info(e.id, fmt.Sprintf("info: %s", v)) 27 | } 28 | 29 | func (e *eventLog) Warning(v string) { 30 | e.l.Warning(e.id, fmt.Sprintf("warning: %s", v)) 31 | } 32 | func (e *eventLog) Error(v string) { 33 | e.l.Error(e.id, fmt.Sprintf("error: %s", v)) 34 | } 35 | -------------------------------------------------------------------------------- /snmp/asn1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /snmp/get.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "fmt" 5 | 6 | "bosun.org/snmp/mib" 7 | ) 8 | 9 | // Get is a wrapper for SNMP.Get. 10 | func Get(host, community string, nameval ...interface{}) error { 11 | s, err := New(host, community) 12 | if err != nil { 13 | return err 14 | } 15 | return s.Get(nameval...) 16 | } 17 | 18 | // Get retrieves an object by its name. Nameval is a pair of: object name or OID 19 | // (string), and the corresponding target value (pointer to int, string, etc.). 20 | // To retrieve multiple objects in a single transaction, provide multiple name, 21 | // value pairs. 22 | func (s *SNMP) Get(nameval ...interface{}) error { 23 | switch n := len(nameval); { 24 | case n == 0: 25 | return nil 26 | case n%2 == 1: 27 | panic("snmp.Get: odd-sized nameval") 28 | } 29 | bindings, err := fromPairs(nameval) 30 | if err != nil { 31 | return err 32 | } 33 | req := &request{ 34 | Type: "Get", 35 | Bindings: bindings, 36 | ID: <-nextID, 37 | } 38 | resp, err := s.do(req) 39 | if err != nil { 40 | return err 41 | } 42 | if err := check(resp, req); err != nil { 43 | return err 44 | } 45 | for i, b := range resp.Bindings { 46 | if have, want := b.Name, req.Bindings[i].Name; !have.Equal(want) { 47 | return fmt.Errorf("snmp: get %v: invalid response: name mismatch", want) 48 | } 49 | v := nameval[2*i+1] 50 | if err := b.unmarshal(v); err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | // fromPairs creates bindings from the (name, value) pairs. 58 | func fromPairs(nameval []interface{}) ([]binding, error) { 59 | var bindings []binding 60 | for i := 0; i < len(nameval); i += 2 { 61 | s, ok := nameval[i].(string) 62 | if !ok { 63 | panic("pair name not a string") 64 | } 65 | oid, err := mib.Lookup(s) 66 | if err != nil { 67 | return nil, err 68 | } 69 | bindings = append(bindings, binding{Name: oid}) 70 | } 71 | return bindings, nil 72 | } 73 | -------------------------------------------------------------------------------- /snmp/snmp_test.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package snmp 4 | 5 | const ( 6 | stringType = "SNMPv2-MIB::sysDescr.0" 7 | oidType = "SNMPv2-MIB::sysObjectID.0" 8 | timeticksType = "HOST-RESOURCES-MIB::hrSystemUptime.0" 9 | counter32Type = "IF-MIB::ifOutOctets.1" 10 | counter64Type = "IF-MIB::ifHCOutOctets.1" 11 | gauge32Type = "IF-MIB::ifSpeed.1" 12 | ) 13 | -------------------------------------------------------------------------------- /snmp/walk_test.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package snmp 4 | 5 | import ( 6 | _ "fmt" 7 | "testing" 8 | _ "time" 9 | ) 10 | 11 | func TestWalk(t *testing.T) { 12 | s, err := Walk("localhost", "public", "IF-MIB::ifDescr", "IF-MIB::ifMtu") 13 | if err != nil { 14 | t.Errorf("unexpected error: %v", err) 15 | return 16 | } 17 | for s.Next() { 18 | var a, b interface{} 19 | _, err := s.Scan(&a, &b) 20 | if err != nil { 21 | t.Errorf("unexpected error: %v", err) 22 | break 23 | } 24 | if a == nil || b == nil { 25 | t.Errorf("unexpected nil") 26 | break 27 | } 28 | if _, ok := a.([]byte); !ok { 29 | t.Errorf("unexpected response: %T", a) 30 | break 31 | } 32 | if _, ok := b.(int64); !ok { 33 | t.Errorf("unexpected response: %T", b) 34 | break 35 | } 36 | // t.Log(string(a.([]byte)), b) 37 | } 38 | if err := s.Err(); err != nil { 39 | t.Errorf("unexpected error: %v", err) 40 | return 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /util/json.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/json" 7 | ) 8 | 9 | func MarshalGzipJson(data interface{}) ([]byte, error) { 10 | buf := &bytes.Buffer{} 11 | g := gzip.NewWriter(buf) 12 | enc := json.NewEncoder(g) 13 | if err := enc.Encode(data); err != nil { 14 | return nil, err 15 | } 16 | if err := g.Flush(); err != nil { 17 | return nil, err 18 | } 19 | return buf.Bytes(), nil 20 | } 21 | 22 | func UnmarshalGzipJson(b []byte, dst interface{}) error { 23 | r := bytes.NewReader(b) 24 | g, err := gzip.NewReader(r) 25 | if err != nil { 26 | return err 27 | } 28 | dec := json.NewDecoder(g) 29 | return dec.Decode(dst) 30 | } 31 | -------------------------------------------------------------------------------- /util/proxy.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httputil" 6 | "net/url" 7 | ) 8 | 9 | // Creates a new http Proxy that forwards requests to the specified url. 10 | // Differs from httputil.NewSingleHostReverseProxy only in that it properly sets the host header. 11 | func NewSingleHostProxy(target *url.URL) *httputil.ReverseProxy { 12 | proxy := httputil.NewSingleHostReverseProxy(target) 13 | director := func(req *http.Request) { 14 | proxy.Director(req) 15 | req.Host = target.Host 16 | } 17 | return &httputil.ReverseProxy{Director: director} 18 | } 19 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Package util defines utilities for bosun. 2 | package util // import "bosun.org/util" 3 | 4 | import ( 5 | "bosun.org/host" 6 | "bosun.org/slog" 7 | 8 | "regexp" 9 | ) 10 | 11 | // This is here only until we manage to refactor more of the system, allowing us to pass a host.Manager around 12 | // the system, rather than holding onto global state 13 | var hostManager host.Manager 14 | 15 | func InitHostManager(customHostname string, useFullHostName bool) { 16 | var hm host.Manager 17 | var err error 18 | 19 | if customHostname != "" { 20 | hm, err = host.NewManagerForHostname(customHostname, useFullHostName) 21 | } else { 22 | hm, err = host.NewManager(useFullHostName) 23 | } 24 | 25 | if err != nil { 26 | slog.Fatalf("couldn't initialise host factory: %v", err) 27 | } 28 | 29 | SetHostManager(hm) 30 | } 31 | 32 | func SetHostManager(hm host.Manager) { 33 | hostManager = hm 34 | } 35 | 36 | func GetHostManager() host.Manager { 37 | return hostManager 38 | } 39 | 40 | func NameMatches(name string, regexes []*regexp.Regexp) bool { 41 | for _, r := range regexes { 42 | if r.MatchString(name) { 43 | return true 44 | } 45 | } 46 | return false 47 | } 48 | 49 | func Btoi(b bool) int { 50 | if b { 51 | return 1 52 | } 53 | return 0 54 | } 55 | --------------------------------------------------------------------------------