├── .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 |
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 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Id
26 | StartDate
27 | EndDate
28 | Source
29 | Host
30 | CreationUser
31 | Owner
32 | Category
33 | Url
34 | Message
35 |
36 |
37 |
38 |
39 |
40 | Edit
41 | Delete
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {{url(a.Url)}}
51 |
52 |
53 |
54 |
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 | {{$k}} {{$v}}
18 | {{else}}
19 | {{$k}} {{$v}}
20 | {{end}}
21 | {{end}}
22 |
23 |
24 | Computation
25 |
26 |
27 | {{range .Computations}}
28 | {{.Text}} {{.Value}}
29 | {{end}}
30 |
`
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 |
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 | Value
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{computations.length - 3}} more ...
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/dashboard.html:
--------------------------------------------------------------------------------
1 |
6 |
11 |
35 |
36 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/errors.html:
--------------------------------------------------------------------------------
1 | Errors
2 |
3 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 |
16 |
17 |
18 | Clear All {{totalLines()}}
19 | Clear {{selectedLines()}} selected
20 |
21 |
22 |
23 |
24 |
alert {{err.Name}} - ({{err.Sum}} events)
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {{line.Message}} - {{line.Count}} times From
To
33 | -
Rule Page
34 |
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 |
6 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/items.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
Metrics
7 |
12 |
15 |
16 |
17 |
Hosts
18 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/note.html:
--------------------------------------------------------------------------------
1 | Add Note
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/notification.html:
--------------------------------------------------------------------------------
1 |
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 |
33 |
34 |
{{k}}:
35 |
{{v}}
36 |
37 |
38 |
39 |
Test {{ct.dat.msg}}
40 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/purge.html:
--------------------------------------------------------------------------------
1 | Purge
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/results.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
Executing: {{running}}
9 |
10 |
11 |
12 |
13 |
Queries
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | group
25 | result
26 | computations
27 |
28 |
29 |
30 |
31 |
32 | {
33 |
34 | {{k}}={{v}},
35 |
36 | }
37 |
38 |
39 | show
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/cmd/bosun/web/static/partials/tokenList.html:
--------------------------------------------------------------------------------
1 | {{ct.status}}
2 | Access Tokens
3 |
4 |
5 |
6 | ID
7 | User
8 | Description
9 | Permissions
10 | Last Used
11 |
12 |
13 |
14 |
15 |
16 | {{tok.Hash | limitTo: 6}}
17 | {{tok.User}}
18 | {{tok.Description}}
19 |
20 | {{tok.RoleName}}
21 |
22 | Never
23 |
24 |
25 |
26 |
27 |
28 |
29 | Create new token
30 |
31 |
32 |
33 |
34 |
38 |
39 |
Really delete token?
40 |
41 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/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 | Email address
11 |
12 |
13 | Submit
14 | Success! Check your email!
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 |
--------------------------------------------------------------------------------