pax_global_header00006660000000000000000000000064132311630720014510gustar00rootroot0000000000000052 comment=193293fc367201f659ae06a7f5c49ebf73f358ae prometheus-2.1.0+ds/000077500000000000000000000000001323116307200143055ustar00rootroot00000000000000prometheus-2.1.0+ds/.dockerignore000066400000000000000000000000571323116307200167630ustar00rootroot00000000000000data/ .build/ .tarballs/ !.build/linux-amd64/ prometheus-2.1.0+ds/.github/000077500000000000000000000000001323116307200156455ustar00rootroot00000000000000prometheus-2.1.0+ds/.github/ISSUE_TEMPLATE.md000066400000000000000000000017071323116307200203570ustar00rootroot00000000000000 **What did you do?** **What did you expect to see?** **What did you see instead? Under which circumstances?** **Environment** * System information: insert output of `uname -srm` here * Prometheus version: insert output of `prometheus --version` here * Alertmanager version: insert output of `alertmanager --version` here (if relevant to the issue) * Prometheus configuration file: ``` insert configuration here ``` * Alertmanager configuration file: ``` insert configuration here (if relevant to the issue) ``` * Logs: ``` insert Prometheus and Alertmanager logs relevant to the issue here ``` prometheus-2.1.0+ds/.gitignore000066400000000000000000000007101323116307200162730ustar00rootroot00000000000000*# .#* *-stamp /*.yaml /*.yml /*.rules *.exe # Editor files # ################ *~ .*.swp .*.swo *.iml .idea tags .history .vscode /prometheus /promtool benchmark.txt /data /cmd/prometheus/data /cmd/prometheus/debug /.build /.release /.tarballs !/circle.yml !/.travis.yml !/.promu.yml /documentation/examples/remote_storage/remote_storage_adapter/remote_storage_adapter /documentation/examples/remote_storage/example_write_adapter/example_writer_adapter prometheus-2.1.0+ds/.promu.yml000066400000000000000000000027501323116307200162540ustar00rootroot00000000000000repository: path: github.com/prometheus/prometheus build: binaries: - name: prometheus path: ./cmd/prometheus - name: promtool path: ./cmd/promtool flags: -a -tags netgo ldflags: | -X {{repoPath}}/vendor/github.com/prometheus/common/version.Version={{.Version}} -X {{repoPath}}/vendor/github.com/prometheus/common/version.Revision={{.Revision}} -X {{repoPath}}/vendor/github.com/prometheus/common/version.Branch={{.Branch}} -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: files: - consoles - console_libraries - documentation/examples/prometheus.yml - LICENSE - NOTICE crossbuild: platforms: - linux/amd64 - linux/386 - darwin/amd64 - darwin/386 - windows/amd64 - windows/386 - freebsd/amd64 - freebsd/386 - openbsd/amd64 - openbsd/386 - netbsd/amd64 - netbsd/386 - dragonfly/amd64 - linux/arm - linux/arm64 - freebsd/arm # Temporarily deactivated as golang.org/x/sys does not have syscalls # implemented for that os/platform combination. #- openbsd/arm #- linux/mips64 #- linux/mips64le - netbsd/arm - linux/ppc64 - linux/ppc64le prometheus-2.1.0+ds/.travis.yml000066400000000000000000000002241323116307200164140ustar00rootroot00000000000000sudo: false language: go go: - 1.9.x - 1.x go_import_path: github.com/prometheus/prometheus script: - make check_license style test staticcheck prometheus-2.1.0+ds/CHANGELOG.md000066400000000000000000001504351323116307200161260ustar00rootroot00000000000000## 2.1.0 / 2018-1-19 * [FEATURE] New Service Discovery UI showing labels before and after relabelling. * [FEATURE] New Admin APIs added to v1 to delete, snapshot and remove tombstones. * [ENHANCEMENT] The graph UI autcomplete now includes your previous queries. * [ENHANCEMENT] Federation is now much faster for large numbers of series. * [ENHANCEMENT] Added new metrics to measure rule timings. * [ENHANCEMENT] Rule evaluation times added to the rules UI. * [ENHANCEMENT] Added metrics to measure modified time of file SD files. * [ENHANCEMENT] Kubernetes SD now includes POD UID in discovery metadata. * [ENHANCEMENT] The Query APIs now return optional stats on query execution times. * [ENHANCEMENT] The index now no longer has the 4GiB size limit and is also smaller. * [BUGFIX] Remote read `read_recent` option is now false by default. * [BUGFIX] Pass the right configuration to each Alertmanager (AM) when using multiple AM configs. * [BUGFIX] Fix not-matchers not selecting series with labels unset. * [BUGFIX] tsdb: Fix occasional panic in head block. * [BUGFIX] tsdb: Close files before deletion to fix retention issues on Windows and NFS. * [BUGFIX] tsdb: Cleanup and do not retry failing compactions. * [BUGFIX] tsdb: Close WAL while shutting down. ## 2.0.0 / 2017-11-08 This release includes a completely rewritten storage, huge performance improvements, but also many backwards incompatible changes. For more information, read the announcement blog post and migration guide. https://prometheus.io/blog/2017/11/08/announcing-prometheus-2-0/ https://prometheus.io/docs/prometheus/2.0/migration/ * [CHANGE] Completely rewritten storage layer, with WAL. This is not backwards compatible with 1.x storage, and many flags have changed/disappeared. * [CHANGE] New staleness behavior. Series now marked stale after target scrapes no longer return them, and soon after targets disappear from service discovery. * [CHANGE] Rules files use YAML syntax now. Conversion tool added to promtool. * [CHANGE] Removed `count_scalar`, `drop_common_labels` functions and `keep_common` modifier from PromQL. * [CHANGE] Rewritten exposition format parser with much higher performance. The Protobuf exposition format is no longer supported. * [CHANGE] Example console templates updated for new storage and metrics names. Examples other than node exporter and Prometheus removed. * [CHANGE] Admin and lifecycle APIs now disabled by default, can be reenabled via flags * [CHANGE] Flags switched to using Kingpin, all flags are now --flagname rather than -flagname. * [FEATURE/CHANGE] Remote read can be configured to not read data which is available locally. This is enabled by default. * [FEATURE] Rules can be grouped now. Rules within a rule group are executed sequentially. * [FEATURE] Added experimental GRPC apis * [FEATURE] Add timestamp() function to PromQL. * [ENHANCEMENT] Remove remote read from the query path if no remote storage is configured. * [ENHANCEMENT] Bump Consul HTTP client timeout to not match the Consul SD watch timeout. * [ENHANCEMENT] Go-conntrack added to provide HTTP connection metrics. * [BUGFIX] Fix connection leak in Consul SD. ## 1.8.2 / 2017-11-04 * [BUGFIX] EC2 service discovery: Do not crash if tags are empty. ## 1.8.1 / 2017-10-19 * [BUGFIX] Correctly handle external labels on remote read endpoint ## 1.8.0 / 2017-10-06 * [CHANGE] Rule links link to the _Console_ tab rather than the _Graph_ tab to not trigger expensive range queries by default. * [FEATURE] Ability to act as a remote read endpoint for other Prometheus servers. * [FEATURE] K8s SD: Support discovery of ingresses. * [FEATURE] Consul SD: Support for node metadata. * [FEATURE] Openstack SD: Support discovery of hypervisors. * [FEATURE] Expose current Prometheus config via `/status/config`. * [FEATURE] Allow to collapse jobs on `/targets` page. * [FEATURE] Add `/-/healthy` and `/-/ready` endpoints. * [FEATURE] Add color scheme support to console templates. * [ENHANCEMENT] Remote storage connections use HTTP keep-alive. * [ENHANCEMENT] Improved logging about remote storage. * [ENHANCEMENT] Relaxed URL validation. * [ENHANCEMENT] Openstack SD: Handle instances without IP. * [ENHANCEMENT] Make remote storage queue manager configurable. * [ENHANCEMENT] Validate metrics returned from remote read. * [ENHANCEMENT] EC2 SD: Set a default region. * [ENHANCEMENT] Changed help link to `https://prometheus.io/docs`. * [BUGFIX] Fix floating-point precision issue in `deriv` function. * [BUGFIX] Fix pprof endpoints when -web.route-prefix or -web.external-url is used. * [BUGFIX] Fix handling of `null` target groups in file-based SD. * [BUGFIX] Set the sample timestamp in date-related PromQL functions. * [BUGFIX] Apply path prefix to redirect from deprecated graph URL. * [BUGFIX] Fixed tests on MS Windows. * [BUGFIX] Check for invalid UTF-8 in label values after relabeling. ## 1.7.2 / 2017-09-26 * [BUGFIX] Correctly remove all targets from DNS service discovery if the corresponding DNS query succeeds and returns an empty result. * [BUGFIX] Correctly parse resolution input in expression browser. * [BUGFIX] Consistently use UTC in the date picker of the expression browser. * [BUGFIX] Correctly handle multiple ports in Marathon service discovery. * [BUGFIX] Fix HTML escaping so that HTML templates compile with Go1.9. * [BUGFIX] Prevent number of remote write shards from going negative. * [BUGFIX] In the graphs created by the expression browser, render very large and small numbers in a readable way. * [BUGFIX] Fix a rarely occurring iterator issue in varbit encoded chunks. ## 1.7.1 / 2017-06-12 * [BUGFIX] Fix double prefix redirect. ## 1.7.0 / 2017-06-06 * [CHANGE] Compress remote storage requests and responses with unframed/raw snappy. * [CHANGE] Properly ellide secrets in config. * [FEATURE] Add OpenStack service discovery. * [FEATURE] Add ability to limit Kubernetes service discovery to certain namespaces. * [FEATURE] Add metric for discovered number of Alertmanagers. * [ENHANCEMENT] Print system information (uname) on start up. * [ENHANCEMENT] Show gaps in graphs on expression browser. * [ENHANCEMENT] Promtool linter checks counter naming and more reserved labels. * [BUGFIX] Fix broken Mesos discovery. * [BUGFIX] Fix redirect when external URL is set. * [BUGFIX] Fix mutation of active alert elements by notifier. * [BUGFIX] Fix HTTP error handling for remote write. * [BUGFIX] Fix builds for Solaris/Illumos. * [BUGFIX] Fix overflow checking in global config. * [BUGFIX] Fix log level reporting issue. * [BUGFIX] Fix ZooKeeper serverset discovery can become out-of-sync. ## 1.6.3 / 2017-05-18 * [BUGFIX] Fix disappearing Alertmanger targets in Alertmanager discovery. * [BUGFIX] Fix panic with remote_write on ARMv7. * [BUGFIX] Fix stacked graphs to adapt min/max values. ## 1.6.2 / 2017-05-11 * [BUGFIX] Fix potential memory leak in Kubernetes service discovery ## 1.6.1 / 2017-04-19 * [BUGFIX] Don't panic if storage has no FPs even after initial wait ## 1.6.0 / 2017-04-14 * [CHANGE] Replaced the remote write implementations for various backends by a generic write interface with example adapter implementation for various backends. Note that both the previous and the current remote write implementations are **experimental**. * [FEATURE] New flag `-storage.local.target-heap-size` to tell Prometheus about the desired heap size. This deprecates the flags `-storage.local.memory-chunks` and `-storage.local.max-chunks-to-persist`, which are kept for backward compatibility. * [FEATURE] Add `check-metrics` to `promtool` to lint metric names. * [FEATURE] Add Joyent Triton discovery. * [FEATURE] `X-Prometheus-Scrape-Timeout-Seconds` header in HTTP scrape requests. * [FEATURE] Remote read interface, including example for InfluxDB. **Experimental.** * [FEATURE] Enable Consul SD to connect via TLS. * [FEATURE] Marathon SD supports multiple ports. * [FEATURE] Marathon SD supports bearer token for authentication. * [FEATURE] Custom timeout for queries. * [FEATURE] Expose `buildQueryUrl` in `graph.js`. * [FEATURE] Add `rickshawGraph` property to the graph object in console templates. * [FEATURE] New metrics exported by Prometheus itself: * Summary `prometheus_engine_query_duration_seconds` * Counter `prometheus_evaluator_iterations_missed_total` * Counter `prometheus_evaluator_iterations_total` * Gauge `prometheus_local_storage_open_head_chunks` * Gauge `prometheus_local_storage_target_heap_size` * [ENHANCEMENT] Reduce shut-down time by interrupting an ongoing checkpoint before starting the final checkpoint. * [ENHANCEMENT] Auto-tweak times between checkpoints to limit time spent in checkpointing to 50%. * [ENHANCEMENT] Improved crash recovery deals better with certain index corruptions. * [ENHANCEMENT] Graphing deals better with constant time series. * [ENHANCEMENT] Retry remote writes on recoverable errors. * [ENHANCEMENT] Evict unused chunk descriptors during crash recovery to limit memory usage. * [ENHANCEMENT] Smoother disk usage during series maintenance. * [ENHANCEMENT] Targets on targets page sorted by instance within a job. * [ENHANCEMENT] Sort labels in federation. * [ENHANCEMENT] Set `GOGC=40` by default, which results in much better memory utilization at the price of slightly higher CPU usage. If `GOGC` is set by the user, it is still honored as usual. * [ENHANCEMENT] Close head chunks after being idle for the duration of the configured staleness delta. This helps to persist and evict head chunk of stale series more quickly. * [ENHANCEMENT] Stricter checking of relabel config. * [ENHANCEMENT] Cache busters for static web content. * [ENHANCEMENT] Send Prometheus-specific user-agent header during scrapes. * [ENHANCEMENT] Improved performance of series retention cut-off. * [ENHANCEMENT] Mitigate impact of non-atomic sample ingestion on `histogram_quantile` by enforcing buckets to be monotonic. * [ENHANCEMENT] Released binaries built with Go 1.8.1. * [BUGFIX] Send `instance=""` with federation if `instance` not set. * [BUGFIX] Update to new `client_golang` to get rid of unwanted quantile metrics in summaries. * [BUGFIX] Introduce several additional guards against data corruption. * [BUGFIX] Mark storage dirty and increment `prometheus_local_storage_persist_errors_total` on all relevant errors. * [BUGFIX] Propagate storage errors as 500 in the HTTP API. * [BUGFIX] Fix int64 overflow in timestamps in the HTTP API. * [BUGFIX] Fix deadlock in Zookeeper SD. * [BUGFIX] Fix fuzzy search problems in the web-UI auto-completion. ## 1.5.3 / 2017-05-11 * [BUGFIX] Fix potential memory leak in Kubernetes service discovery ## 1.5.2 / 2017-02-10 * [BUGFIX] Fix series corruption in a special case of series maintenance where the minimum series-file-shrink-ratio kicks in. * [BUGFIX] Fix two panic conditions both related to processing a series scheduled to be quarantined. * [ENHANCEMENT] Binaries built with Go1.7.5. ## 1.5.1 / 2017-02-07 * [BUGFIX] Don't lose fully persisted memory series during checkpointing. * [BUGFIX] Fix intermittently failing relabeling. * [BUGFIX] Make `-storage.local.series-file-shrink-ratio` work. * [BUGFIX] Remove race condition from TestLoop. ## 1.5.0 / 2017-01-23 * [CHANGE] Use lexicographic order to sort alerts by name. * [FEATURE] Add Joyent Triton discovery. * [FEATURE] Add scrape targets and alertmanager targets API. * [FEATURE] Add various persistence related metrics. * [FEATURE] Add various query engine related metrics. * [FEATURE] Add ability to limit scrape samples, and related metrics. * [FEATURE] Add labeldrop and labelkeep relabelling actions. * [FEATURE] Display current working directory on status-page. * [ENHANCEMENT] Strictly use ServiceAccount for in cluster configuration on Kubernetes. * [ENHANCEMENT] Various performance and memory-management improvements. * [BUGFIX] Fix basic auth for alertmanagers configured via flag. * [BUGFIX] Don't panic on decoding corrupt data. * [BUGFIX] Ignore dotfiles in data directory. * [BUGFIX] Abort on intermediate federation errors. ## 1.4.1 / 2016-11-28 * [BUGFIX] Fix Consul service discovery ## 1.4.0 / 2016-11-25 * [FEATURE] Allow configuring Alertmanagers via service discovery * [FEATURE] Display used Alertmanagers on runtime page in the UI * [FEATURE] Support profiles in AWS EC2 service discovery configuration * [ENHANCEMENT] Remove duplicated logging of Kubernetes client errors * [ENHANCEMENT] Add metrics about Kubernetes service discovery * [BUGFIX] Update alert annotations on re-evaluation * [BUGFIX] Fix export of group modifier in PromQL queries * [BUGFIX] Remove potential deadlocks in several service discovery implementations * [BUGFIX] Use proper float64 modulo in PromQL `%` binary operations * [BUGFIX] Fix crash bug in Kubernetes service discovery ## 1.3.1 / 2016-11-04 This bug-fix release pulls in the fixes from the 1.2.3 release. * [BUGFIX] Correctly handle empty Regex entry in relabel config. * [BUGFIX] MOD (`%`) operator doesn't panic with small floating point numbers. * [BUGFIX] Updated miekg/dns vendoring to pick up upstream bug fixes. * [ENHANCEMENT] Improved DNS error reporting. ## 1.2.3 / 2016-11-04 Note that this release is chronologically after 1.3.0. * [BUGFIX] Correctly handle end time before start time in range queries. * [BUGFIX] Error on negative `-storage.staleness-delta` * [BUGFIX] Correctly handle empty Regex entry in relabel config. * [BUGFIX] MOD (`%`) operator doesn't panic with small floating point numbers. * [BUGFIX] Updated miekg/dns vendoring to pick up upstream bug fixes. * [ENHANCEMENT] Improved DNS error reporting. ## 1.3.0 / 2016-11-01 This is a breaking change to the Kubernetes service discovery. * [CHANGE] Rework Kubernetes SD. * [FEATURE] Add support for interpolating `target_label`. * [FEATURE] Add GCE metadata as Prometheus meta labels. * [ENHANCEMENT] Add EC2 SD metrics. * [ENHANCEMENT] Add Azure SD metrics. * [ENHANCEMENT] Add fuzzy search to `/graph` textarea. * [ENHANCEMENT] Always show instance labels on target page. * [BUGFIX] Validate query end time is not before start time. * [BUGFIX] Error on negative `-storage.staleness-delta` ## 1.2.2 / 2016-10-30 * [BUGFIX] Correctly handle on() in alerts. * [BUGFIX] UI: Deal properly with aborted requests. * [BUGFIX] UI: Decode URL query parameters properly. * [BUGFIX] Storage: Deal better with data corruption (non-monotonic timestamps). * [BUGFIX] Remote storage: Re-add accidentally removed timeout flag. * [BUGFIX] Updated a number of vendored packages to pick up upstream bug fixes. ## 1.2.1 / 2016-10-10 * [BUGFIX] Count chunk evictions properly so that the server doesn't assume it runs out of memory and subsequencly throttles ingestion. * [BUGFIX] Use Go1.7.1 for prebuilt binaries to fix issues on MacOS Sierra. ## 1.2.0 / 2016-10-07 * [FEATURE] Cleaner encoding of query parameters in `/graph` URLs. * [FEATURE] PromQL: Add `minute()` function. * [FEATURE] Add GCE service discovery. * [FEATURE] Allow any valid UTF-8 string as job name. * [FEATURE] Allow disabling local storage. * [FEATURE] EC2 service discovery: Expose `ec2_instance_state`. * [ENHANCEMENT] Various performance improvements in local storage. * [BUGFIX] Zookeeper service discovery: Remove deleted nodes. * [BUGFIX] Zookeeper service discovery: Resync state after Zookeeper failure. * [BUGFIX] Remove JSON from HTTP Accept header. * [BUGFIX] Fix flag validation of Alertmanager URL. * [BUGFIX] Fix race condition on shutdown. * [BUGFIX] Do not fail Consul discovery on Prometheus startup when Consul is down. * [BUGFIX] Handle NaN in `changes()` correctly. * [CHANGE] **Experimental** remote write path: Remove use of gRPC. * [CHANGE] **Experimental** remote write path: Configuration via config file rather than command line flags. * [FEATURE] **Experimental** remote write path: Add HTTP basic auth and TLS. * [FEATURE] **Experimental** remote write path: Support for relabelling. ## 1.1.3 / 2016-09-16 * [ENHANCEMENT] Use golang-builder base image for tests in CircleCI. * [ENHANCEMENT] Added unit tests for federation. * [BUGFIX] Correctly de-dup metric families in federation output. ## 1.1.2 / 2016-09-08 * [BUGFIX] Allow label names that coincide with keywords. ## 1.1.1 / 2016-09-07 * [BUGFIX] Fix IPv6 escaping in service discovery integrations * [BUGFIX] Fix default scrape port assignment for IPv6 ## 1.1.0 / 2016-09-03 * [FEATURE] Add `quantile` and `quantile_over_time`. * [FEATURE] Add `stddev_over_time` and `stdvar_over_time`. * [FEATURE] Add various time and date functions. * [FEATURE] Added `toUpper` and `toLower` formatting to templates. * [FEATURE] Allow relabeling of alerts. * [FEATURE] Allow URLs in targets defined via a JSON file. * [FEATURE] Add idelta function. * [FEATURE] 'Remove graph' button on the /graph page. * [FEATURE] Kubernetes SD: Add node name and host IP to pod discovery. * [FEATURE] New remote storage write path. EXPERIMENTAL! * [ENHANCEMENT] Improve time-series index lookups. * [ENHANCEMENT] Forbid invalid relabel configurations. * [ENHANCEMENT] Improved various tests. * [ENHANCEMENT] Add crash recovery metric 'started_dirty'. * [ENHANCEMENT] Fix (and simplify) populating series iterators. * [ENHANCEMENT] Add job link on target page. * [ENHANCEMENT] Message on empty Alerts page. * [ENHANCEMENT] Various internal code refactorings and clean-ups. * [ENHANCEMENT] Various improvements in the build system. * [BUGFIX] Catch errors when unmarshalling delta/doubleDelta encoded chunks. * [BUGFIX] Fix data race in lexer and lexer test. * [BUGFIX] Trim stray whitespace from bearer token file. * [BUGFIX] Avoid divide-by-zero panic on query_range?step=0. * [BUGFIX] Detect invalid rule files at startup. * [BUGFIX] Fix counter reset treatment in PromQL. * [BUGFIX] Fix rule HTML escaping issues. * [BUGFIX] Remove internal labels from alerts sent to AM. ## 1.0.2 / 2016-08-24 * [BUGFIX] Clean up old targets after config reload. ## 1.0.1 / 2016-07-21 * [BUGFIX] Exit with error on non-flag command-line arguments. * [BUGFIX] Update example console templates to new HTTP API. * [BUGFIX] Re-add logging flags. ## 1.0.0 / 2016-07-18 * [CHANGE] Remove deprecated query language keywords * [CHANGE] Change Kubernetes SD to require specifying Kubernetes role * [CHANGE] Use service address in Consul SD if available * [CHANGE] Standardize all Prometheus internal metrics to second units * [CHANGE] Remove unversioned legacy HTTP API * [CHANGE] Remove legacy ingestion of JSON metric format * [CHANGE] Remove deprecated `target_groups` configuration * [FEATURE] Add binary power operation to PromQL * [FEATURE] Add `count_values` aggregator * [FEATURE] Add `-web.route-prefix` flag * [FEATURE] Allow `on()`, `by()`, `without()` in PromQL with empty label sets * [ENHANCEMENT] Make `topk/bottomk` query functions aggregators * [BUGFIX] Fix annotations in alert rule printing * [BUGFIX] Expand alert templating at evaluation time * [BUGFIX] Fix edge case handling in crash recovery * [BUGFIX] Hide testing package flags from help output ## 0.20.0 / 2016-06-15 This release contains multiple breaking changes to the configuration schema. * [FEATURE] Allow configuring multiple Alertmanagers * [FEATURE] Add server name to TLS configuration * [FEATURE] Add labels for all node addresses and discover node port if available in Kubernetes SD * [ENHANCEMENT] More meaningful configuration errors * [ENHANCEMENT] Round scraping timestamps to milliseconds in web UI * [ENHANCEMENT] Make number of storage fingerprint locks configurable * [BUGFIX] Fix date parsing in console template graphs * [BUGFIX] Fix static console files in Docker images * [BUGFIX] Fix console JS XHR requests for IE11 * [BUGFIX] Add missing path prefix in new status page * [CHANGE] Rename `target_groups` to `static_configs` in config files * [CHANGE] Rename `names` to `files` in file SD configuration * [CHANGE] Remove kubelet port config option in Kubernetes SD configuration ## 0.19.3 / 2016-06-14 * [BUGFIX] Handle Marathon apps with zero ports * [BUGFIX] Fix startup panic in retrieval layer ## 0.19.2 / 2016-05-29 * [BUGFIX] Correctly handle `GROUP_LEFT` and `GROUP_RIGHT` without labels in string representation of expressions and in rules. * [BUGFIX] Use `-web.external-url` for new status endpoints. ## 0.19.1 / 2016-05-25 * [BUGFIX] Handle service discovery panic affecting Kubernetes SD * [BUGFIX] Fix web UI display issue in some browsers ## 0.19.0 / 2016-05-24 This version contains a breaking change to the query language. Please read the documentation on the grouping behavior of vector matching: https://prometheus.io/docs/querying/operators/#vector-matching * [FEATURE] Add experimental Microsoft Azure service discovery * [FEATURE] Add `ignoring` modifier for binary operations * [FEATURE] Add pod discovery to Kubernetes service discovery * [CHANGE] Vector matching takes grouping labels from one-side * [ENHANCEMENT] Support time range on /api/v1/series endpoint * [ENHANCEMENT] Partition status page into individual pages * [BUGFIX] Fix issue of hanging target scrapes ## 0.18.0 / 2016-04-18 * [BUGFIX] Fix operator precedence in PromQL * [BUGFIX] Never drop still open head chunk * [BUGFIX] Fix missing 'keep_common' when printing AST node * [CHANGE/BUGFIX] Target identity considers path and parameters additionally to host and port * [CHANGE] Rename metric `prometheus_local_storage_invalid_preload_requests_total` to `prometheus_local_storage_non_existent_series_matches_total` * [CHANGE] Support for old alerting rule syntax dropped * [FEATURE] Deduplicate targets within the same scrape job * [FEATURE] Add varbit chunk encoding (higher compression, more CPU usage – disabled by default) * [FEATURE] Add `holt_winters` query function * [FEATURE] Add relative complement `unless` operator to PromQL * [ENHANCEMENT] Quarantine series file if data corruption is encountered (instead of crashing) * [ENHANCEMENT] Validate Alertmanager URL * [ENHANCEMENT] Use UTC for build timestamp * [ENHANCEMENT] Improve index query performance (especially for active time series) * [ENHANCEMENT] Instrument configuration reload duration * [ENHANCEMENT] Instrument retrieval layer * [ENHANCEMENT] Add Go version to `prometheus_build_info` metric ## 0.17.0 / 2016-03-02 This version no longer works with Alertmanager 0.0.4 and earlier! The alerting rule syntax has changed as well but the old syntax is supported up until version 0.18. All regular expressions in PromQL are anchored now, matching the behavior of regular expressions in config files. * [CHANGE] Integrate with Alertmanager 0.1.0 and higher * [CHANGE] Degraded storage mode renamed to rushed mode * [CHANGE] New alerting rule syntax * [CHANGE] Add label validation on ingestion * [CHANGE] Regular expression matchers in PromQL are anchored * [FEATURE] Add `without` aggregation modifier * [FEATURE] Send alert resolved notifications to Alertmanager * [FEATURE] Allow millisecond precision in configuration file * [FEATURE] Support AirBnB's Smartstack Nerve for service discovery * [ENHANCEMENT] Storage switches less often between regular and rushed mode. * [ENHANCEMENT] Storage switches into rushed mode if there are too many memory chunks. * [ENHANCEMENT] Added more storage instrumentation * [ENHANCEMENT] Improved instrumentation of notification handler * [BUGFIX] Do not count head chunks as chunks waiting for persistence * [BUGFIX] Handle OPTIONS HTTP requests to the API correctly * [BUGFIX] Parsing of ranges in PromQL fixed * [BUGFIX] Correctly validate URL flag parameters * [BUGFIX] Log argument parse errors * [BUGFIX] Properly handle creation of target with bad TLS config * [BUGFIX] Fix of checkpoint timing issue ## 0.16.2 / 2016-01-18 * [FEATURE] Multiple authentication options for EC2 discovery added * [FEATURE] Several meta labels for EC2 discovery added * [FEATURE] Allow full URLs in static target groups (used e.g. by the `blackbox_exporter`) * [FEATURE] Add Graphite remote-storage integration * [FEATURE] Create separate Kubernetes targets for services and their endpoints * [FEATURE] Add `clamp_{min,max}` functions to PromQL * [FEATURE] Omitted time parameter in API query defaults to now * [ENHANCEMENT] Less frequent time series file truncation * [ENHANCEMENT] Instrument number of manually deleted time series * [ENHANCEMENT] Ignore lost+found directory during storage version detection * [CHANGE] Kubernetes `masters` renamed to `api_servers` * [CHANGE] "Healthy" and "unhealthy" targets are now called "up" and "down" in the web UI * [CHANGE] Remove undocumented 2nd argument of the `delta` function. (This is a BREAKING CHANGE for users of the undocumented 2nd argument.) * [BUGFIX] Return proper HTTP status codes on API errors * [BUGFIX] Fix Kubernetes authentication configuration * [BUGFIX] Fix stripped OFFSET from in rule evaluation and display * [BUGFIX] Do not crash on failing Consul SD initialization * [BUGFIX] Revert changes to metric auto-completion * [BUGFIX] Add config overflow validation for TLS configuration * [BUGFIX] Skip already watched Zookeeper nodes in serverset SD * [BUGFIX] Don't federate stale samples * [BUGFIX] Move NaN to end of result for `topk/bottomk/sort/sort_desc/min/max` * [BUGFIX] Limit extrapolation of `delta/rate/increase` * [BUGFIX] Fix unhandled error in rule evaluation Some changes to the Kubernetes service discovery were integration since it was released as a beta feature. ## 0.16.1 / 2015-10-16 * [FEATURE] Add `irate()` function. * [ENHANCEMENT] Improved auto-completion in expression browser. * [CHANGE] Kubernetes SD moves node label to instance label. * [BUGFIX] Escape regexes in console templates. ## 0.16.0 / 2015-10-09 BREAKING CHANGES: * Release tarballs now contain the built binaries in a nested directory. * The `hash_mod` relabeling action now uses MD5 hashes instead of FNV hashes to achieve a better distribution. * The DNS-SD meta label `__meta_dns_srv_name` was renamed to `__meta_dns_name` to reflect support for DNS record types other than `SRV`. * The default full refresh interval for the file-based service discovery has been increased from 30 seconds to 5 minutes. * In relabeling, parts of a source label that weren't matched by the specified regular expression are no longer included in the replacement output. * Queries no longer interpolate between two data points. Instead, the resulting value will always be the latest value before the evaluation query timestamp. * Regular expressions supplied via the configuration are now anchored to match full strings instead of substrings. * Global labels are not appended upon storing time series anymore. Instead, they are only appended when communicating with external systems (Alertmanager, remote storages, federation). They have thus also been renamed from `global.labels` to `global.external_labels`. * The names and units of metrics related to remote storage sample appends have been changed. * The experimental support for writing to InfluxDB has been updated to work with InfluxDB 0.9.x. 0.8.x versions of InfluxDB are not supported anymore. * Escape sequences in double- and single-quoted string literals in rules or query expressions are now interpreted like escape sequences in Go string literals (https://golang.org/ref/spec#String_literals). Future breaking changes / deprecated features: * The `delta()` function had an undocumented optional second boolean argument to make it behave like `increase()`. This second argument will be removed in the future. Migrate any occurrences of `delta(x, 1)` to use `increase(x)` instead. * Support for filter operators between two scalar values (like `2 > 1`) will be removed in the future. These will require a `bool` modifier on the operator, e.g. `2 > bool 1`. All changes: * [CHANGE] Renamed `global.labels` to `global.external_labels`. * [CHANGE] Vendoring is now done via govendor instead of godep. * [CHANGE] Change web UI root page to show the graphing interface instead of the server status page. * [CHANGE] Append global labels only when communicating with external systems instead of storing them locally. * [CHANGE] Change all regexes in the configuration to do full-string matches instead of substring matches. * [CHANGE] Remove interpolation of vector values in queries. * [CHANGE] For alert `SUMMARY`/`DESCRIPTION` template fields, cast the alert value to `float64` to work with common templating functions. * [CHANGE] In relabeling, don't include unmatched source label parts in the replacement. * [CHANGE] Change default full refresh interval for the file-based service discovery from 30 seconds to 5 minutes. * [CHANGE] Rename the DNS-SD meta label `__meta_dns_srv_name` to `__meta_dns_name` to reflect support for other record types than `SRV`. * [CHANGE] Release tarballs now contain the binaries in a nested directory. * [CHANGE] Update InfluxDB write support to work with InfluxDB 0.9.x. * [FEATURE] Support full "Go-style" escape sequences in strings and add raw string literals. * [FEATURE] Add EC2 service discovery support. * [FEATURE] Allow configuring TLS options in scrape configurations. * [FEATURE] Add instrumentation around configuration reloads. * [FEATURE] Add `bool` modifier to comparison operators to enable boolean (`0`/`1`) output instead of filtering. * [FEATURE] In Zookeeper serverset discovery, provide `__meta_serverset_shard` label with the serverset shard number. * [FEATURE] Provide `__meta_consul_service_id` meta label in Consul service discovery. * [FEATURE] Allow scalar expressions in recording rules to enable use cases such as building constant metrics. * [FEATURE] Add `label_replace()` and `vector()` query language functions. * [FEATURE] In Consul service discovery, fill in the `__meta_consul_dc` datacenter label from the Consul agent when it's not set in the Consul SD config. * [FEATURE] Scrape all services upon empty services list in Consul service discovery. * [FEATURE] Add `labelmap` relabeling action to map a set of input labels to a set of output labels using regular expressions. * [FEATURE] Introduce `__tmp` as a relabeling label prefix that is guaranteed to not be used by Prometheus internally. * [FEATURE] Kubernetes-based service discovery. * [FEATURE] Marathon-based service discovery. * [FEATURE] Support multiple series names in console graphs JavaScript library. * [FEATURE] Allow reloading configuration via web handler at `/-/reload`. * [FEATURE] Updates to promtool to reflect new Prometheus configuration features. * [FEATURE] Add `proxy_url` parameter to scrape configurations to enable use of proxy servers. * [FEATURE] Add console templates for Prometheus itself. * [FEATURE] Allow relabeling the protocol scheme of targets. * [FEATURE] Add `predict_linear()` query language function. * [FEATURE] Support for authentication using bearer tokens, client certs, and CA certs. * [FEATURE] Implement unary expressions for vector types (`-foo`, `+foo`). * [FEATURE] Add console templates for the SNMP exporter. * [FEATURE] Make it possible to relabel target scrape query parameters. * [FEATURE] Add support for `A` and `AAAA` records in DNS service discovery. * [ENHANCEMENT] Fix several flaky tests. * [ENHANCEMENT] Switch to common routing package. * [ENHANCEMENT] Use more resilient metric decoder. * [ENHANCEMENT] Update vendored dependencies. * [ENHANCEMENT] Add compression to more HTTP handlers. * [ENHANCEMENT] Make -web.external-url flag help string more verbose. * [ENHANCEMENT] Improve metrics around remote storage queues. * [ENHANCEMENT] Use Go 1.5.1 instead of Go 1.4.2 in builds. * [ENHANCEMENT] Update the architecture diagram in the `README.md`. * [ENHANCEMENT] Time out sample appends in retrieval layer if the storage is backlogging. * [ENHANCEMENT] Make `hash_mod` relabeling action use MD5 instead of FNV to enable better hash distribution. * [ENHANCEMENT] Better tracking of targets between same service discovery mechanisms in one scrape configuration. * [ENHANCEMENT] Handle parser and query evaluation runtime panics more gracefully. * [ENHANCEMENT] Add IDs to H2 tags on status page to allow anchored linking. * [BUGFIX] Fix watching multiple paths with Zookeeper serverset discovery. * [BUGFIX] Fix high CPU usage on configuration reload. * [BUGFIX] Fix disappearing `__params` on configuration reload. * [BUGFIX] Make `labelmap` action available through configuration. * [BUGFIX] Fix direct access of protobuf fields. * [BUGFIX] Fix panic on Consul request error. * [BUGFIX] Redirect of graph endpoint for prefixed setups. * [BUGFIX] Fix series file deletion behavior when purging archived series. * [BUGFIX] Fix error checking and logging around checkpointing. * [BUGFIX] Fix map initialization in target manager. * [BUGFIX] Fix draining of file watcher events in file-based service discovery. * [BUGFIX] Add `POST` handler for `/debug` endpoints to fix CPU profiling. * [BUGFIX] Fix several flaky tests. * [BUGFIX] Fix busylooping in case a scrape configuration has no target providers defined. * [BUGFIX] Fix exit behavior of static target provider. * [BUGFIX] Fix configuration reloading loop upon shutdown. * [BUGFIX] Add missing check for nil expression in expression parser. * [BUGFIX] Fix error handling bug in test code. * [BUGFIX] Fix Consul port meta label. * [BUGFIX] Fix lexer bug that treated non-Latin Unicode digits as digits. * [CLEANUP] Remove obsolete federation example from console templates. * [CLEANUP] Remove duplicated Bootstrap JS inclusion on graph page. * [CLEANUP] Switch to common log package. * [CLEANUP] Update build environment scripts and Makefiles to work better with native Go build mechanisms and new Go 1.5 experimental vendoring support. * [CLEANUP] Remove logged notice about 0.14.x configuration file format change. * [CLEANUP] Move scrape-time metric label modification into SampleAppenders. * [CLEANUP] Switch from `github.com/client_golang/model` to `github.com/common/model` and related type cleanups. * [CLEANUP] Switch from `github.com/client_golang/extraction` to `github.com/common/expfmt` and related type cleanups. * [CLEANUP] Exit Prometheus when the web server encounters a startup error. * [CLEANUP] Remove non-functional alert-silencing links on alerting page. * [CLEANUP] General cleanups to comments and code, derived from `golint`, `go vet`, or otherwise. * [CLEANUP] When entering crash recovery, tell users how to cleanly shut down Prometheus. * [CLEANUP] Remove internal support for multi-statement queries in query engine. * [CLEANUP] Update AUTHORS.md. * [CLEANUP] Don't warn/increment metric upon encountering equal timestamps for the same series upon append. * [CLEANUP] Resolve relative paths during configuration loading. ## 0.15.1 / 2015-07-27 * [BUGFIX] Fix vector matching behavior when there is a mix of equality and non-equality matchers in a vector selector and one matcher matches no series. * [ENHANCEMENT] Allow overriding `GOARCH` and `GOOS` in Makefile.INCLUDE. * [ENHANCEMENT] Update vendored dependencies. ## 0.15.0 / 2015-07-21 BREAKING CHANGES: * Relative paths for rule files are now evaluated relative to the config file. * External reachability flags (`-web.*`) consolidated. * The default storage directory has been changed from `/tmp/metrics` to `data` in the local directory. * The `rule_checker` tool has been replaced by `promtool` with different flags and more functionality. * Empty labels are now removed upon ingestion into the storage. Matching empty labels is now equivalent to matching unset labels (`mymetric{label=""}` now matches series that don't have `label` set at all). * The special `__meta_consul_tags` label in Consul service discovery now starts and ends with tag separators to enable easier regex matching. * The default scrape interval has been changed back from 1 minute to 10 seconds. All changes: * [CHANGE] Change default storage directory to `data` in the current working directory. * [CHANGE] Consolidate external reachability flags (`-web.*`)into one. * [CHANGE] Deprecate `keeping_extra` modifier keyword, rename it to `keep_common`. * [CHANGE] Improve label matching performance and treat unset labels like empty labels in label matchers. * [CHANGE] Remove `rule_checker` tool and add generic `promtool` CLI tool which allows checking rules and configuration files. * [CHANGE] Resolve rule files relative to config file. * [CHANGE] Restore default ScrapeInterval of 1 minute instead of 10 seconds. * [CHANGE] Surround `__meta_consul_tags` value with tag separators. * [CHANGE] Update node disk console for new filesystem labels. * [FEATURE] Add Consul's `ServiceAddress`, `Address`, and `ServicePort` as meta labels to enable setting a custom scrape address if needed. * [FEATURE] Add `hashmod` relabel action to allow for horizontal sharding of Prometheus servers. * [FEATURE] Add `honor_labels` scrape configuration option to not overwrite any labels exposed by the target. * [FEATURE] Add basic federation support on `/federate`. * [FEATURE] Add optional `RUNBOOK` field to alert statements. * [FEATURE] Add pre-relabel target labels to status page. * [FEATURE] Add version information endpoint under `/version`. * [FEATURE] Added initial stable API version 1 under `/api/v1`, including ability to delete series and query more metadata. * [FEATURE] Allow configuring query parameters when scraping metrics endpoints. * [FEATURE] Allow deleting time series via the new v1 API. * [FEATURE] Allow individual ingested metrics to be relabeled. * [FEATURE] Allow loading rule files from an entire directory. * [FEATURE] Allow scalar expressions in range queries, improve error messages. * [FEATURE] Support Zookeeper Serversets as a service discovery mechanism. * [ENHANCEMENT] Add circleci yaml for Dockerfile test build. * [ENHANCEMENT] Always show selected graph range, regardless of available data. * [ENHANCEMENT] Change expression input field to multi-line textarea. * [ENHANCEMENT] Enforce strict monotonicity of time stamps within a series. * [ENHANCEMENT] Export build information as metric. * [ENHANCEMENT] Improve UI of `/alerts` page. * [ENHANCEMENT] Improve display of target labels on status page. * [ENHANCEMENT] Improve initialization and routing functionality of web service. * [ENHANCEMENT] Improve target URL handling and display. * [ENHANCEMENT] New dockerfile using alpine-glibc base image and make. * [ENHANCEMENT] Other minor fixes. * [ENHANCEMENT] Preserve alert state across reloads. * [ENHANCEMENT] Prettify flag help output even more. * [ENHANCEMENT] README.md updates. * [ENHANCEMENT] Raise error on unknown config parameters. * [ENHANCEMENT] Refine v1 HTTP API output. * [ENHANCEMENT] Show original configuration file contents on status page instead of serialized YAML. * [ENHANCEMENT] Start HUP signal handler earlier to not exit upon HUP during startup. * [ENHANCEMENT] Updated vendored dependencies. * [BUGFIX] Do not panic in `StringToDuration()` on wrong duration unit. * [BUGFIX] Exit on invalid rule files on startup. * [BUGFIX] Fix a regression in the `.Path` console template variable. * [BUGFIX] Fix chunk descriptor loading. * [BUGFIX] Fix consoles "Prometheus" link to point to / * [BUGFIX] Fix empty configuration file cases * [BUGFIX] Fix float to int conversions in chunk encoding, which were broken for some architectures. * [BUGFIX] Fix overflow detection for serverset config. * [BUGFIX] Fix race conditions in retrieval layer. * [BUGFIX] Fix shutdown deadlock in Consul SD code. * [BUGFIX] Fix the race condition targets in the Makefile. * [BUGFIX] Fix value display error in web console. * [BUGFIX] Hide authentication credentials in config `String()` output. * [BUGFIX] Increment dirty counter metric in storage only if `setDirty(true)` is called. * [BUGFIX] Periodically refresh services in Consul to recover from missing events. * [BUGFIX] Prevent overwrite of default global config when loading a configuration. * [BUGFIX] Properly lex `\r` as whitespace in expression language. * [BUGFIX] Validate label names in JSON target groups. * [BUGFIX] Validate presence of regex field in relabeling configurations. * [CLEANUP] Clean up initialization of remote storage queues. * [CLEANUP] Fix `go vet` and `golint` violations. * [CLEANUP] General cleanup of rules and query language code. * [CLEANUP] Improve and simplify Dockerfile build steps. * [CLEANUP] Improve and simplify build infrastructure, use go-bindata for web assets. Allow building without git. * [CLEANUP] Move all utility packages into common `util` subdirectory. * [CLEANUP] Refactor main, flag handling, and web package. * [CLEANUP] Remove unused methods from `Rule` interface. * [CLEANUP] Simplify default config handling. * [CLEANUP] Switch human-readable times on web UI to UTC. * [CLEANUP] Use `templates.TemplateExpander` for all page templates. * [CLEANUP] Use new v1 HTTP API for querying and graphing. ## 0.14.0 / 2015-06-01 * [CHANGE] Configuration format changed and switched to YAML. (See the provided [migration tool](https://github.com/prometheus/migrate/releases).) * [ENHANCEMENT] Redesign of state-preserving target discovery. * [ENHANCEMENT] Allow specifying scrape URL scheme and basic HTTP auth for non-static targets. * [FEATURE] Allow attaching meaningful labels to targets via relabeling. * [FEATURE] Configuration/rule reloading at runtime. * [FEATURE] Target discovery via file watches. * [FEATURE] Target discovery via Consul. * [ENHANCEMENT] Simplified binary operation evaluation. * [ENHANCEMENT] More stable component initialization. * [ENHANCEMENT] Added internal expression testing language. * [BUGFIX] Fix graph links with path prefix. * [ENHANCEMENT] Allow building from source without git. * [ENHANCEMENT] Improve storage iterator performance. * [ENHANCEMENT] Change logging output format and flags. * [BUGFIX] Fix memory alignment bug for 32bit systems. * [ENHANCEMENT] Improve web redirection behavior. * [ENHANCEMENT] Allow overriding default hostname for Prometheus URLs. * [BUGFIX] Fix double slash in URL sent to alertmanager. * [FEATURE] Add resets() query function to count counter resets. * [FEATURE] Add changes() query function to count the number of times a gauge changed. * [FEATURE] Add increase() query function to calculate a counter's increase. * [ENHANCEMENT] Limit retrievable samples to the storage's retention window. ## 0.13.4 / 2015-05-23 * [BUGFIX] Fix a race while checkpointing fingerprint mappings. ## 0.13.3 / 2015-05-11 * [BUGFIX] Handle fingerprint collisions properly. * [CHANGE] Comments in rules file must start with `#`. (The undocumented `//` and `/*...*/` comment styles are no longer supported.) * [ENHANCEMENT] Switch to custom expression language parser and evaluation engine, which generates better error messages, fixes some parsing edge-cases, and enables other future enhancements (like the ones below). * [ENHANCEMENT] Limit maximum number of concurrent queries. * [ENHANCEMENT] Terminate running queries during shutdown. ## 0.13.2 / 2015-05-05 * [MAINTENANCE] Updated vendored dependencies to their newest versions. * [MAINTENANCE] Include rule_checker and console templates in release tarball. * [BUGFIX] Sort NaN as the lowest value. * [ENHANCEMENT] Add square root, stddev and stdvar functions. * [BUGFIX] Use scrape_timeout for scrape timeout, not scrape_interval. * [ENHANCEMENT] Improve chunk and chunkDesc loading, increase performance when reading from disk. * [BUGFIX] Show correct error on wrong DNS response. ## 0.13.1 / 2015-04-09 * [BUGFIX] Treat memory series with zero chunks correctly in series maintenance. * [ENHANCEMENT] Improve readability of usage text even more. ## 0.13.0 / 2015-04-08 * [ENHANCEMENT] Double-delta encoding for chunks, saving typically 40% of space, both in RAM and on disk. * [ENHANCEMENT] Redesign of chunk persistence queuing, increasing performance on spinning disks significantly. * [ENHANCEMENT] Redesign of sample ingestion, increasing ingestion performance. * [FEATURE] Added ln, log2, log10 and exp functions to the query language. * [FEATURE] Experimental write support to InfluxDB. * [FEATURE] Allow custom timestamps in instant query API. * [FEATURE] Configurable path prefix for URLs to support proxies. * [ENHANCEMENT] Increase of rule_checker CLI usability. * [CHANGE] Show special float values as gaps. * [ENHANCEMENT] Made usage output more readable. * [ENHANCEMENT] Increased resilience of the storage against data corruption. * [ENHANCEMENT] Various improvements around chunk encoding. * [ENHANCEMENT] Nicer formatting of target health table on /status. * [CHANGE] Rename UNREACHABLE to UNHEALTHY, ALIVE to HEALTHY. * [BUGFIX] Strip trailing slash in alertmanager URL. * [BUGFIX] Avoid +InfYs and similar, just display +Inf. * [BUGFIX] Fixed HTML-escaping at various places. * [BUGFIX] Fixed special value handling in division and modulo of the query language. * [BUGFIX] Fix embed-static.sh. * [CLEANUP] Added intial HTTP API tests. * [CLEANUP] Misc. other code cleanups. * [MAINTENANCE] Updated vendored dependcies to their newest versions. ## 0.12.0 / 2015-03-04 * [CHANGE] Use client_golang v0.3.1. THIS CHANGES FINGERPRINTING AND INVALIDATES ALL PERSISTED FINGERPRINTS. You have to wipe your storage to use this or later versions. There is a version guard in place that will prevent you to run Prometheus with the stored data of an older Prometheus. * [BUGFIX] The change above fixes a weakness in the fingerprinting algorithm. * [ENHANCEMENT] The change above makes fingerprinting faster and less allocation intensive. * [FEATURE] OR operator and vector matching options. See docs for details. * [ENHANCEMENT] Scientific notation and special float values (Inf, NaN) now supported by the expression language. * [CHANGE] Dockerfile makes Prometheus use the Docker volume to store data (rather than /tmp/metrics). * [CHANGE] Makefile uses Go 1.4.2. ## 0.11.1 / 2015-02-27 * [BUGFIX] Make series maintenance complete again. (Ever since 0.9.0rc4, or commit 0851945, series would not be archived, chunk descriptors would not be evicted, and stale head chunks would never be closed. This happened due to accidental deletion of a line calling a (well tested :) function. * [BUGFIX] Do not double count head chunks read from checkpoint on startup. Also fix a related but less severe bug in counting chunk descriptors. * [BUGFIX] Check last time in head chunk for head chunk timeout, not first. * [CHANGE] Update vendoring due to vendoring changes in client_golang. * [CLEANUP] Code cleanups. * [ENHANCEMENT] Limit the number of 'dirty' series counted during checkpointing. ## 0.11.0 / 2015-02-23 * [FEATURE] Introduce new metric type Histogram with server-side aggregation. * [FEATURE] Add offset operator. * [FEATURE] Add floor, ceil and round functions. * [CHANGE] Change instance identifiers to be host:port. * [CHANGE] Dependency management and vendoring changed/improved. * [CHANGE] Flag name changes to create consistency between various Prometheus binaries. * [CHANGE] Show unlimited number of metrics in autocomplete. * [CHANGE] Add query timeout. * [CHANGE] Remove labels on persist error counter. * [ENHANCEMENT] Various performance improvements for sample ingestion. * [ENHANCEMENT] Various Makefile improvements. * [ENHANCEMENT] Various console template improvements, including proof-of-concept for federation via console templates. * [ENHANCEMENT] Fix graph JS glitches and simplify graphing code. * [ENHANCEMENT] Dramatically decrease resources for file embedding. * [ENHANCEMENT] Crash recovery saves lost series data in 'orphaned' directory. * [BUGFIX] Fix aggregation grouping key calculation. * [BUGFIX] Fix Go download path for various architectures. * [BUGFIX] Fixed the link of the Travis build status image. * [BUGFIX] Fix Rickshaw/D3 version mismatch. * [CLEANUP] Various code cleanups. ## 0.10.0 / 2015-01-26 * [CHANGE] More efficient JSON result format in query API. This requires up-to-date versions of PromDash and prometheus_cli, too. * [ENHANCEMENT] Excluded non-minified Bootstrap assets and the Bootstrap maps from embedding into the binary. Those files are only used for debugging, and then you can use -web.use-local-assets. By including fewer files, the RAM usage during compilation is much more manageable. * [ENHANCEMENT] Help link points to http://prometheus.github.io now. * [FEATURE] Consoles for haproxy and cloudwatch. * [BUGFIX] Several fixes to graphs in consoles. * [CLEANUP] Removed a file size check that did not check anything. ## 0.9.0 / 2015-01-23 * [CHANGE] Reworked command line flags, now more consistent and taking into account needs of the new storage backend (see below). * [CHANGE] Metric names are dropped after certain transformations. * [CHANGE] Changed partitioning of summary metrics exported by Prometheus. * [CHANGE] Got rid of Gerrit as a review tool. * [CHANGE] 'Tabular' view now the default (rather than 'Graph') to avoid running very expensive queries accidentally. * [CHANGE] On-disk format for stored samples changed. For upgrading, you have to nuke your old files completely. See "Complete rewrite of the storage * [CHANGE] Removed 2nd argument from `delta`. * [FEATURE] Added a `deriv` function. * [FEATURE] Console templates. * [FEATURE] Added `absent` function. * [FEATURE] Allow omitting the metric name in queries. * [BUGFIX] Removed all known race conditions. * [BUGFIX] Metric mutations now handled correctly in all cases. * [ENHANCEMENT] Proper double-start protection. * [ENHANCEMENT] Complete rewrite of the storage layer. Benefits include: * Better query performance. * More samples in less RAM. * Better memory management. * Scales up to millions of time series and thousands of samples ingested per second. * Purging of obsolete samples much cleaner now, up to completely "forgetting" obsolete time series. * Proper instrumentation to diagnose the storage layer with... well... Prometheus. * Pure Go implementation, no need for cgo and shared C libraries anymore. * Better concurrency. * [ENHANCEMENT] Copy-on-write semantics in the AST layer. * [ENHANCEMENT] Switched from Go 1.3 to Go 1.4. * [ENHANCEMENT] Vendored external dependencies with godeps. * [ENHANCEMENT] Numerous Web UI improvements, moved to Bootstrap3 and Rickshaw 1.5.1. * [ENHANCEMENT] Improved Docker integration. * [ENHANCEMENT] Simplified the Makefile contraption. * [CLEANUP] Put meta-data files into proper shape (LICENSE, README.md etc.) * [CLEANUP] Removed all legitimate 'go vet' and 'golint' warnings. * [CLEANUP] Removed dead code. ## 0.8.0 / 2014-09-04 * [ENHANCEMENT] Stagger scrapes to spread out load. * [BUGFIX] Correctly quote HTTP Accept header. ## 0.7.0 / 2014-08-06 * [FEATURE] Added new functions: abs(), topk(), bottomk(), drop_common_labels(). * [FEATURE] Let console templates get graph links from expressions. * [FEATURE] Allow console templates to dynamically include other templates. * [FEATURE] Template consoles now have access to their URL. * [BUGFIX] Fixed time() function to return evaluation time, not wallclock time. * [BUGFIX] Fixed HTTP connection leak when targets returned a non-200 status. * [BUGFIX] Fixed link to console templates in UI. * [PERFORMANCE] Removed extra memory copies while scraping targets. * [ENHANCEMENT] Switched from Go 1.2.1 to Go 1.3. * [ENHANCEMENT] Made metrics exported by Prometheus itself more consistent. * [ENHANCEMENT] Removed incremental backoffs for unhealthy targets. * [ENHANCEMENT] Dockerfile also builds Prometheus support tools now. ## 0.6.0 / 2014-06-30 * [FEATURE] Added console and alert templates support, along with various template functions. * [PERFORMANCE] Much faster and more memory-efficient flushing to disk. * [ENHANCEMENT] Query results are now only logged when debugging. * [ENHANCEMENT] Upgraded to new Prometheus client library for exposing metrics. * [BUGFIX] Samples are now kept in memory until fully flushed to disk. * [BUGFIX] Non-200 target scrapes are now treated as an error. * [BUGFIX] Added installation step for missing dependency to Dockerfile. * [BUGFIX] Removed broken and unused "User Dashboard" link. ## 0.5.0 / 2014-05-28 * [BUGFIX] Fixed next retrieval time display on status page. * [BUGFIX] Updated some variable references in tools subdir. * [FEATURE] Added support for scraping metrics via the new text format. * [PERFORMANCE] Improved label matcher performance. * [PERFORMANCE] Removed JSON indentation in query API, leading to smaller response sizes. * [ENHANCEMENT] Added internal check to verify temporal order of streams. * [ENHANCEMENT] Some internal refactorings. ## 0.4.0 / 2014-04-17 * [FEATURE] Vectors and scalars may now be reversed in binary operations (` `). * [FEATURE] It's possible to shutdown Prometheus via a `/-/quit` web endpoint now. * [BUGFIX] Fix for a deadlock race condition in the memory storage. * [BUGFIX] Mac OS X build fixed. * [BUGFIX] Built from Go 1.2.1, which has internal fixes to race conditions in garbage collection handling. * [ENHANCEMENT] Internal storage interface refactoring that allows building e.g. the `rule_checker` tool without LevelDB dynamic library dependencies. * [ENHANCEMENT] Cleanups around shutdown handling. * [PERFORMANCE] Preparations for better memory reuse during marshalling / unmarshalling. prometheus-2.1.0+ds/CONTRIBUTING.md000066400000000000000000000057161323116307200165470ustar00rootroot00000000000000# Contributing Prometheus uses GitHub to manage reviews of pull requests. * If you are a new contributor see: [Steps to Contribute](#steps-to-contribute) * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) a suitable maintainer of this repository (see [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. * If you plan to do something more involved, first discuss your ideas on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). This will avoid unnecessary work and surely give you and us a good deal of inspiration. * Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). ## Steps to Contribute Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue. Please check the [`low-hanging-fruit`](https://github.com/prometheus/prometheus/issues?q=is%3Aissue+is%3Aopen+label%3A%22low+hanging+fruit%22) label to find issues that are good for getting started. If you have questions about one of the issues, with or without the tag, please comment on them and one of the maintainers will clarify it. For a quicker response, contact us over [IRC](https://prometheus.io/community). For complete instructions on how to compile see: [Building From Source](https://github.com/prometheus/prometheus#building-from-source) For quickly compiling and testing your changes do: ``` # For building. go build ./cmd/prometheus/ ./prometheus # For testing. make test # Make sure all the tests pass before you commit and push :) ``` All our issues are regularly tagged so that you can also filter down the issues involving the components you want to work on. For our labelling policy refer [the wiki page](https://github.com/prometheus/prometheus/wiki/Label-Names-and-Descriptions). ## Pull Request Checklist * Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes. * Commits should be as small as possible, while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). * If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review on IRC channel [#prometheus](https://webchat.freenode.net/?channels=#prometheus) on irc.freenode.net (for the easiest start, [join via Riot](https://riot.im/app/#/room/#prometheus:matrix.org)). * Add tests relevant to the fixed bug or new feature. prometheus-2.1.0+ds/Dockerfile000066400000000000000000000020401323116307200162730ustar00rootroot00000000000000FROM quay.io/prometheus/busybox:latest LABEL maintainer "The Prometheus Authors " COPY prometheus /bin/prometheus COPY promtool /bin/promtool COPY documentation/examples/prometheus.yml /etc/prometheus/prometheus.yml COPY console_libraries/ /usr/share/prometheus/console_libraries/ COPY consoles/ /usr/share/prometheus/consoles/ RUN ln -s /usr/share/prometheus/console_libraries /usr/share/prometheus/consoles/ /etc/prometheus/ RUN mkdir -p /prometheus && \ chown -R nobody:nogroup etc/prometheus /prometheus USER nobody EXPOSE 9090 VOLUME [ "/prometheus" ] WORKDIR /prometheus ENTRYPOINT [ "/bin/prometheus" ] CMD [ "--config.file=/etc/prometheus/prometheus.yml", \ "--storage.tsdb.path=/prometheus", \ "--web.console.libraries=/usr/share/prometheus/console_libraries", \ "--web.console.templates=/usr/share/prometheus/consoles" ] prometheus-2.1.0+ds/LICENSE000066400000000000000000000261351323116307200153210ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. prometheus-2.1.0+ds/MAINTAINERS.md000066400000000000000000000006701323116307200164040ustar00rootroot00000000000000Maintainers of this repository with their focus areas: * Brian Brazil @brian-brazil: Console templates; semantics of PromQL, service discovery, and relabeling. * Fabian Reinartz @fabxc: PromQL parsing and evaluation; implementation of retrieval, alert notification, and service discovery. * Julius Volz @juliusv: Remote storage integrations; web UI. prometheus-2.1.0+ds/Makefile000066400000000000000000000064551323116307200157570ustar00rootroot00000000000000# Copyright 2015 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) PROMU := $(FIRST_GOPATH)/bin/promu STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck pkgs = $(shell $(GO) list ./... | grep -v /vendor/) PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_NAME ?= prometheus DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) ifdef DEBUG bindata_flags = -debug endif STATICCHECK_IGNORE = \ github.com/prometheus/prometheus/discovery/kubernetes/kubernetes.go:SA1019 \ github.com/prometheus/prometheus/discovery/kubernetes/node.go:SA1019 \ github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/main.go:SA1019 \ github.com/prometheus/prometheus/pkg/textparse/lex.l.go:SA4006 \ github.com/prometheus/prometheus/pkg/pool/pool.go:SA6002 \ github.com/prometheus/prometheus/promql/engine.go:SA6002 \ github.com/prometheus/prometheus/web/web.go:SA1019 all: format staticcheck build test style: @echo ">> checking code style" @! $(GOFMT) -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^' check_license: @echo ">> checking license header" @./scripts/check_license.sh # TODO(fabxc): example tests temporarily removed. test-short: @echo ">> running short tests" @$(GO) test -short $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples) test: @echo ">> running all tests" @$(GO) test $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples) format: @echo ">> formatting code" @$(GO) fmt $(pkgs) vet: @echo ">> vetting code" @$(GO) vet $(pkgs) staticcheck: $(STATICCHECK) @echo ">> running staticcheck" @$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs) build: promu @echo ">> building binaries" @$(PROMU) build --prefix $(PREFIX) tarball: promu @echo ">> building release tarball" @$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) docker: @echo ">> building docker image" @docker build -t "$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" . assets: @echo ">> writing assets" @$(GO) get -u github.com/jteeuwen/go-bindata/... @go-bindata $(bindata_flags) -pkg ui -o web/ui/bindata.go -ignore '(.*\.map|bootstrap\.js|bootstrap-theme\.css|bootstrap\.css)' web/ui/templates/... web/ui/static/... @$(GO) fmt ./web/ui promu: @echo ">> fetching promu" @GOOS=$(shell uname -s | tr A-Z a-z) \ GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \ GO="$(GO)" \ $(GO) get -u github.com/prometheus/promu $(FIRST_GOPATH)/bin/staticcheck: @GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck .PHONY: all style check_license format build test vet assets tarball docker promu staticcheck $(FIRST_GOPATH)/bin/staticcheck prometheus-2.1.0+ds/NOTICE000066400000000000000000000053211323116307200152120ustar00rootroot00000000000000The Prometheus systems and service monitoring server Copyright 2012-2015 The Prometheus Authors This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). The following components are included in this product: Bootstrap http://getbootstrap.com Copyright 2011-2014 Twitter, Inc. Licensed under the MIT License bootstrap3-typeahead.js https://github.com/bassjobsen/Bootstrap-3-Typeahead Original written by @mdo and @fat Copyright 2014 Bass Jobsen @bassjobsen Licensed under the Apache License, Version 2.0 fuzzy https://github.com/mattyork/fuzzy Original written by @mattyork Copyright 2012 Matt York Licensed under the MIT License bootstrap-datetimepicker.js https://github.com/Eonasdan/bootstrap-datetimepicker Copyright 2015 Jonathan Peterson (@Eonasdan) Licensed under the MIT License moment.js https://github.com/moment/moment/ Copyright JS Foundation and other contributors Licensed under the MIT License Rickshaw https://github.com/shutterstock/rickshaw Copyright 2011-2014 by Shutterstock Images, LLC See https://github.com/shutterstock/rickshaw/blob/master/LICENSE for license details mustache.js https://github.com/janl/mustache.js Copyright 2009 Chris Wanstrath (Ruby) Copyright 2010-2014 Jan Lehnardt (JavaScript) Copyright 2010-2015 The mustache.js community Licensed under the MIT License jQuery https://jquery.org Copyright jQuery Foundation and other contributors Licensed under the MIT License Protocol Buffers for Go with Gadgets http://github.com/gogo/protobuf/ Copyright (c) 2013, The GoGo Authors. See source code for license details. Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 Support for streaming Protocol Buffer messages for the Go language (golang). https://github.com/matttproud/golang_protobuf_extensions Copyright 2013 Matt T. Proud Licensed under the Apache License, Version 2.0 DNS library in Go http://miek.nl/posts/2014/Aug/16/go-dns-package/ Copyright 2009 The Go Authors, 2011 Miek Gieben See https://github.com/miekg/dns/blob/master/LICENSE for license details. LevelDB key/value database in Go https://github.com/syndtr/goleveldb Copyright 2012 Suryandaru Triandana See https://github.com/syndtr/goleveldb/blob/master/LICENSE for license details. gosnappy - a fork of code.google.com/p/snappy-go https://github.com/syndtr/gosnappy Copyright 2011 The Snappy-Go Authors See https://github.com/syndtr/gosnappy/blob/master/LICENSE for license details. go-zookeeper - Native ZooKeeper client for Go https://github.com/samuel/go-zookeeper Copyright (c) 2013, Samuel Stauffer See https://github.com/samuel/go-zookeeper/blob/master/LICENSE for license details. prometheus-2.1.0+ds/README.md000066400000000000000000000106021323116307200155630ustar00rootroot00000000000000# Prometheus [![Build Status](https://travis-ci.org/prometheus/prometheus.svg)][travis] [![CircleCI](https://circleci.com/gh/prometheus/prometheus/tree/master.svg?style=shield)][circleci] [![Docker Repository on Quay](https://quay.io/repository/prometheus/prometheus/status)][quay] [![Docker Pulls](https://img.shields.io/docker/pulls/prom/prometheus.svg?maxAge=604800)][hub] [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/prometheus)](https://goreportcard.com/report/github.com/prometheus/prometheus) Visit [prometheus.io](https://prometheus.io) for the full documentation, examples and guides. Prometheus, a [Cloud Native Computing Foundation](https://cncf.io/) project, is a systems and service monitoring system. It collects metrics from configured targets at given intervals, evaluates rule expressions, displays the results, and can trigger alerts if some condition is observed to be true. Prometheus' main distinguishing features as compared to other monitoring systems are: - a **multi-dimensional** data model (timeseries defined by metric name and set of key/value dimensions) - a **flexible query language** to leverage this dimensionality - no dependency on distributed storage; **single server nodes are autonomous** - timeseries collection happens via a **pull model** over HTTP - **pushing timeseries** is supported via an intermediary gateway - targets are discovered via **service discovery** or **static configuration** - multiple modes of **graphing and dashboarding support** - support for hierarchical and horizontal **federation** ## Architecture overview ![](https://cdn.rawgit.com/prometheus/prometheus/c34257d069c630685da35bcef084632ffd5d6209/documentation/images/architecture.svg) ## Install There are various ways of installing Prometheus. ### Precompiled binaries Precompiled binaries for released versions are available in the [*download* section](https://prometheus.io/download/) on [prometheus.io](https://prometheus.io). Using the latest production release binary is the recommended way of installing Prometheus. See the [Installing](https://prometheus.io/docs/introduction/install/) chapter in the documentation for all the details. Debian packages [are available](https://packages.debian.org/sid/net/prometheus). ### Docker images Docker images are available on [Quay.io](https://quay.io/repository/prometheus/prometheus). You can launch a Prometheus container for trying it out with $ docker run --name prometheus -d -p 127.0.0.1:9090:9090 quay.io/prometheus/prometheus Prometheus will now be reachable at http://localhost:9090/. ### Building from source To build Prometheus from the source code yourself you need to have a working Go environment with [version 1.9 or greater installed](http://golang.org/doc/install). You can directly use the `go` tool to download and install the `prometheus` and `promtool` binaries into your `GOPATH`: $ go get github.com/prometheus/prometheus/cmd/... $ prometheus --config.file=your_config.yml You can also clone the repository yourself and build using `make`: $ mkdir -p $GOPATH/src/github.com/prometheus $ cd $GOPATH/src/github.com/prometheus $ git clone https://github.com/prometheus/prometheus.git $ cd prometheus $ make build $ ./prometheus --config.file=your_config.yml The Makefile provides several targets: * *build*: build the `prometheus` and `promtool` binaries * *test*: run the tests * *test-short*: run the short tests * *format*: format the source code * *vet*: check the source code for common errors * *assets*: rebuild the static assets * *docker*: build a docker container for the current `HEAD` ## More information * The source code is periodically indexed: [Prometheus Core](http://godoc.org/github.com/prometheus/prometheus). * You will find a Travis CI configuration in `.travis.yml`. * See the [Community page](https://prometheus.io/community) for how to reach the Prometheus developers and users on various communication channels. ## Contributing Refer to [CONTRIBUTING.md](https://github.com/prometheus/prometheus/blob/master/CONTRIBUTING.md) ## License Apache License 2.0, see [LICENSE](https://github.com/prometheus/prometheus/blob/master/LICENSE). [travis]: https://travis-ci.org/prometheus/prometheus [hub]: https://hub.docker.com/r/prom/prometheus/ [circleci]: https://circleci.com/gh/prometheus/prometheus [quay]: https://quay.io/repository/prometheus/prometheus prometheus-2.1.0+ds/VERSION000066400000000000000000000000061323116307200153510ustar00rootroot000000000000002.1.0 prometheus-2.1.0+ds/circle.yml000066400000000000000000000045571323116307200163040ustar00rootroot00000000000000machine: environment: DOCKER_IMAGE_NAME: prom/prometheus QUAY_IMAGE_NAME: quay.io/prometheus/prometheus DOCKER_TEST_IMAGE_NAME: quay.io/prometheus/golang-builder:1.9-base REPO_PATH: github.com/prometheus/prometheus pre: - sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci' - sudo chmod 0755 /usr/bin/docker - sudo curl -L 'https://github.com/aktau/github-release/releases/download/v0.6.2/linux-amd64-github-release.tar.bz2' | tar xvjf - --strip-components 3 -C $HOME/bin services: - docker dependencies: pre: - make promu - docker info override: - promu crossbuild - ln -s .build/linux-amd64/prometheus prometheus - ln -s .build/linux-amd64/promtool promtool - | if [ -n "$CIRCLE_TAG" ]; then make docker DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME DOCKER_IMAGE_TAG=$CIRCLE_TAG make docker DOCKER_IMAGE_NAME=$QUAY_IMAGE_NAME DOCKER_IMAGE_TAG=$CIRCLE_TAG else make docker DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME make docker DOCKER_IMAGE_NAME=$QUAY_IMAGE_NAME fi post: - mkdir $CIRCLE_ARTIFACTS/binaries/ && cp -a .build/* $CIRCLE_ARTIFACTS/binaries/ - docker images test: override: - docker run --rm -t -v "$(pwd):/app" "${DOCKER_TEST_IMAGE_NAME}" -i "${REPO_PATH}" -T deployment: hub_branch: branch: master owner: prometheus commands: - docker login -e $DOCKER_EMAIL -u $DOCKER_LOGIN -p $DOCKER_PASSWORD - docker login -e $QUAY_EMAIL -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io - docker push $DOCKER_IMAGE_NAME - docker push $QUAY_IMAGE_NAME hub_tag: tag: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ owner: prometheus commands: - promu crossbuild tarballs - promu checksum .tarballs - promu release .tarballs - mkdir $CIRCLE_ARTIFACTS/releases/ && cp -a .tarballs/* $CIRCLE_ARTIFACTS/releases/ - docker login -e $DOCKER_EMAIL -u $DOCKER_LOGIN -p $DOCKER_PASSWORD - docker login -e $QUAY_EMAIL -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io - | if [[ "$CIRCLE_TAG" =~ ^v[0-9]+(\.[0-9]+){2}$ ]]; then docker tag "$DOCKER_IMAGE_NAME:$CIRCLE_TAG" "$DOCKER_IMAGE_NAME:latest" docker tag "$QUAY_IMAGE_NAME:$CIRCLE_TAG" "$QUAY_IMAGE_NAME:latest" fi - docker push $DOCKER_IMAGE_NAME - docker push $QUAY_IMAGE_NAME prometheus-2.1.0+ds/cmd/000077500000000000000000000000001323116307200150505ustar00rootroot00000000000000prometheus-2.1.0+ds/cmd/prometheus/000077500000000000000000000000001323116307200172435ustar00rootroot00000000000000prometheus-2.1.0+ds/cmd/prometheus/fdlimits_default.go000066400000000000000000000016671323116307200231230ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows package main import ( "fmt" "log" "syscall" ) // FdLimits returns the soft and hard limits for file descriptors func FdLimits() string { flimit := syscall.Rlimit{} err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &flimit) if err != nil { log.Fatal("Error!") } return fmt.Sprintf("(soft=%d, hard=%d)", flimit.Cur, flimit.Max) } prometheus-2.1.0+ds/cmd/prometheus/fdlimits_windows.go000066400000000000000000000013001323116307200231510ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build windows package main // FdLimits not supported on Windows func FdLimits() string { return "N/A" } prometheus-2.1.0+ds/cmd/prometheus/main.go000066400000000000000000000512631323116307200205250ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // The main package for the Prometheus server executable. package main import ( "context" "crypto/md5" "encoding/json" "fmt" "net" "net/http" _ "net/http/pprof" // Comment this line to disable pprof endpoint. "net/url" "os" "os/signal" "path/filepath" "runtime" "strings" "sync" "syscall" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/oklog/oklog/pkg/group" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/common/version" "gopkg.in/alecthomas/kingpin.v2" k8s_runtime "k8s.io/apimachinery/pkg/util/runtime" "github.com/mwitkow/go-conntrack" "github.com/prometheus/common/promlog" promlogflag "github.com/prometheus/common/promlog/flag" "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/discovery" sd_config "github.com/prometheus/prometheus/discovery/config" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/retrieval" "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage/remote" "github.com/prometheus/prometheus/storage/tsdb" "github.com/prometheus/prometheus/util/strutil" "github.com/prometheus/prometheus/web" ) var ( configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "prometheus", Name: "config_last_reload_successful", Help: "Whether the last configuration reload attempt was successful.", }) configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "prometheus", Name: "config_last_reload_success_timestamp_seconds", Help: "Timestamp of the last successful configuration reload.", }) ) func init() { prometheus.MustRegister(version.NewCollector("prometheus")) } func main() { if os.Getenv("DEBUG") != "" { runtime.SetBlockProfileRate(20) runtime.SetMutexProfileFraction(20) } cfg := struct { configFile string localStoragePath string notifier notifier.Options notifierTimeout model.Duration queryEngine promql.EngineOptions web web.Options tsdb tsdb.Options lookbackDelta model.Duration webTimeout model.Duration queryTimeout model.Duration prometheusURL string logLevel promlog.AllowedLevel }{ notifier: notifier.Options{ Registerer: prometheus.DefaultRegisterer, }, queryEngine: promql.EngineOptions{ Metrics: prometheus.DefaultRegisterer, }, } a := kingpin.New(filepath.Base(os.Args[0]), "The Prometheus monitoring server") a.Version(version.Print("prometheus")) a.HelpFlag.Short('h') a.Flag("config.file", "Prometheus configuration file path."). Default("prometheus.yml").StringVar(&cfg.configFile) a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry."). Default("0.0.0.0:9090").StringVar(&cfg.web.ListenAddress) a.Flag("web.read-timeout", "Maximum duration before timing out read of the request, and closing idle connections."). Default("5m").SetValue(&cfg.webTimeout) a.Flag("web.max-connections", "Maximum number of simultaneous connections."). Default("512").IntVar(&cfg.web.MaxConnections) a.Flag("web.external-url", "The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically."). PlaceHolder("").StringVar(&cfg.prometheusURL) a.Flag("web.route-prefix", "Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url."). PlaceHolder("").StringVar(&cfg.web.RoutePrefix) a.Flag("web.user-assets", "Path to static asset directory, available at /user."). PlaceHolder("").StringVar(&cfg.web.UserAssetsPath) a.Flag("web.enable-lifecycle", "Enable shutdown and reload via HTTP request."). Default("false").BoolVar(&cfg.web.EnableLifecycle) a.Flag("web.enable-admin-api", "Enables API endpoints for admin control actions."). Default("false").BoolVar(&cfg.web.EnableAdminAPI) a.Flag("web.console.templates", "Path to the console template directory, available at /consoles."). Default("consoles").StringVar(&cfg.web.ConsoleTemplatesPath) a.Flag("web.console.libraries", "Path to the console library directory."). Default("console_libraries").StringVar(&cfg.web.ConsoleLibrariesPath) a.Flag("storage.tsdb.path", "Base path for metrics storage."). Default("data/").StringVar(&cfg.localStoragePath) a.Flag("storage.tsdb.min-block-duration", "Minimum duration of a data block before being persisted. For use in testing."). Hidden().Default("2h").SetValue(&cfg.tsdb.MinBlockDuration) a.Flag("storage.tsdb.max-block-duration", "Maximum duration compacted blocks may span. For use in testing. (Defaults to 10% of the retention period)."). Hidden().PlaceHolder("").SetValue(&cfg.tsdb.MaxBlockDuration) a.Flag("storage.tsdb.retention", "How long to retain samples in the storage."). Default("15d").SetValue(&cfg.tsdb.Retention) a.Flag("storage.tsdb.no-lockfile", "Do not create lockfile in data directory."). Default("false").BoolVar(&cfg.tsdb.NoLockfile) a.Flag("alertmanager.notification-queue-capacity", "The capacity of the queue for pending alert manager notifications."). Default("10000").IntVar(&cfg.notifier.QueueCapacity) a.Flag("alertmanager.timeout", "Timeout for sending alerts to Alertmanager."). Default("10s").SetValue(&cfg.notifierTimeout) a.Flag("query.lookback-delta", "The delta difference allowed for retrieving metrics during expression evaluations."). Default("5m").SetValue(&cfg.lookbackDelta) a.Flag("query.timeout", "Maximum time a query may take before being aborted."). Default("2m").SetValue(&cfg.queryTimeout) a.Flag("query.max-concurrency", "Maximum number of queries executed concurrently."). Default("20").IntVar(&cfg.queryEngine.MaxConcurrentQueries) promlogflag.AddFlags(a, &cfg.logLevel) _, err := a.Parse(os.Args[1:]) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments")) a.Usage(os.Args[1:]) os.Exit(2) } cfg.web.ExternalURL, err = computeExternalURL(cfg.prometheusURL, cfg.web.ListenAddress) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrapf(err, "parse external URL %q", cfg.prometheusURL)) os.Exit(2) } cfg.web.ReadTimeout = time.Duration(cfg.webTimeout) // Default -web.route-prefix to path of -web.external-url. if cfg.web.RoutePrefix == "" { cfg.web.RoutePrefix = cfg.web.ExternalURL.Path } // RoutePrefix must always be at least '/'. cfg.web.RoutePrefix = "/" + strings.Trim(cfg.web.RoutePrefix, "/") if cfg.tsdb.MaxBlockDuration == 0 { cfg.tsdb.MaxBlockDuration = cfg.tsdb.Retention / 10 } promql.LookbackDelta = time.Duration(cfg.lookbackDelta) cfg.queryEngine.Timeout = time.Duration(cfg.queryTimeout) logger := promlog.New(cfg.logLevel) // XXX(fabxc): Kubernetes does background logging which we can only customize by modifying // a global variable. // Ultimately, here is the best place to set it. k8s_runtime.ErrorHandlers = []func(error){ func(err error) { level.Error(log.With(logger, "component", "k8s_client_runtime")).Log("err", err) }, } level.Info(logger).Log("msg", "Starting Prometheus", "version", version.Info()) level.Info(logger).Log("build_context", version.BuildContext()) level.Info(logger).Log("host_details", Uname()) level.Info(logger).Log("fd_limits", FdLimits()) var ( localStorage = &tsdb.ReadyStorage{} remoteStorage = remote.NewStorage(log.With(logger, "component", "remote"), localStorage.StartTime) fanoutStorage = storage.NewFanout(logger, localStorage, remoteStorage) ) cfg.queryEngine.Logger = log.With(logger, "component", "query engine") var ( ctxWeb, cancelWeb = context.WithCancel(context.Background()) ctxRule = context.Background() notifier = notifier.New(&cfg.notifier, log.With(logger, "component", "notifier")) discoveryManagerScrape = discovery.NewManager(log.With(logger, "component", "discovery manager scrape")) discoveryManagerNotify = discovery.NewManager(log.With(logger, "component", "discovery manager notify")) scrapeManager = retrieval.NewScrapeManager(log.With(logger, "component", "scrape manager"), fanoutStorage) queryEngine = promql.NewEngine(fanoutStorage, &cfg.queryEngine) ruleManager = rules.NewManager(&rules.ManagerOptions{ Appendable: fanoutStorage, QueryFunc: rules.EngineQueryFunc(queryEngine), NotifyFunc: sendAlerts(notifier, cfg.web.ExternalURL.String()), Context: ctxRule, ExternalURL: cfg.web.ExternalURL, Registerer: prometheus.DefaultRegisterer, Logger: log.With(logger, "component", "rule manager"), }) ) cfg.web.Context = ctxWeb cfg.web.TSDB = localStorage.Get cfg.web.Storage = fanoutStorage cfg.web.QueryEngine = queryEngine cfg.web.ScrapeManager = scrapeManager cfg.web.RuleManager = ruleManager cfg.web.Notifier = notifier cfg.web.Version = &web.PrometheusVersion{ Version: version.Version, Revision: version.Revision, Branch: version.Branch, BuildUser: version.BuildUser, BuildDate: version.BuildDate, GoVersion: version.GoVersion, } cfg.web.Flags = map[string]string{} for _, f := range a.Model().Flags { cfg.web.Flags[f.Name] = f.Value.String() } // Depends on cfg.web.ScrapeManager so needs to be after cfg.web.ScrapeManager = scrapeManager webHandler := web.New(log.With(logger, "component", "web"), &cfg.web) // Monitor outgoing connections on default transport with conntrack. http.DefaultTransport.(*http.Transport).DialContext = conntrack.NewDialContextFunc( conntrack.DialWithTracing(), ) reloaders := []func(cfg *config.Config) error{ remoteStorage.ApplyConfig, webHandler.ApplyConfig, // The Scrape and notifier managers need to reload before the Discovery manager as // they need to read the most updated config when receiving the new targets list. notifier.ApplyConfig, scrapeManager.ApplyConfig, func(cfg *config.Config) error { c := make(map[string]sd_config.ServiceDiscoveryConfig) for _, v := range cfg.ScrapeConfigs { c[v.JobName] = v.ServiceDiscoveryConfig } return discoveryManagerScrape.ApplyConfig(c) }, func(cfg *config.Config) error { c := make(map[string]sd_config.ServiceDiscoveryConfig) for _, v := range cfg.AlertingConfig.AlertmanagerConfigs { // AlertmanagerConfigs doesn't hold an unique identifier so we use the config hash as the identifier. b, err := json.Marshal(v) if err != nil { return err } c[fmt.Sprintf("%x", md5.Sum(b))] = v.ServiceDiscoveryConfig } return discoveryManagerNotify.ApplyConfig(c) }, func(cfg *config.Config) error { // Get all rule files matching the configuration oaths. var files []string for _, pat := range cfg.RuleFiles { fs, err := filepath.Glob(pat) if err != nil { // The only error can be a bad pattern. return fmt.Errorf("error retrieving rule files for %s: %s", pat, err) } files = append(files, fs...) } return ruleManager.Update(time.Duration(cfg.GlobalConfig.EvaluationInterval), files) }, } prometheus.MustRegister(configSuccess) prometheus.MustRegister(configSuccessTime) // Start all components while we wait for TSDB to open but only load // initial config and mark ourselves as ready after it completed. dbOpen := make(chan struct{}) // sync.Once is used to make sure we can close the channel at different execution stages(SIGTERM or when the config is loaded). type closeOnce struct { C chan struct{} once sync.Once Close func() } // Wait until the server is ready to handle reloading. reloadReady := &closeOnce{ C: make(chan struct{}), } reloadReady.Close = func() { reloadReady.once.Do(func() { close(reloadReady.C) }) } var g group.Group { term := make(chan os.Signal) signal.Notify(term, os.Interrupt, syscall.SIGTERM) cancel := make(chan struct{}) g.Add( func() error { // Don't forget to release the reloadReady channel so that waiting blocks can exit normally. select { case <-term: level.Warn(logger).Log("msg", "Received SIGTERM, exiting gracefully...") reloadReady.Close() case <-webHandler.Quit(): level.Warn(logger).Log("msg", "Received termination request via web service, exiting gracefully...") case <-cancel: reloadReady.Close() break } return nil }, func(err error) { close(cancel) }, ) } { ctx, cancel := context.WithCancel(context.Background()) g.Add( func() error { err := discoveryManagerScrape.Run(ctx) level.Info(logger).Log("msg", "Scrape discovery manager stopped") return err }, func(err error) { level.Info(logger).Log("msg", "Stopping scrape discovery manager...") cancel() }, ) } { ctx, cancel := context.WithCancel(context.Background()) g.Add( func() error { err := discoveryManagerNotify.Run(ctx) level.Info(logger).Log("msg", "Notify discovery manager stopped") return err }, func(err error) { level.Info(logger).Log("msg", "Stopping notify discovery manager...") cancel() }, ) } { g.Add( func() error { // When the scrape manager receives a new targets list // it needs to read a valid config for each job. // It depends on the config being in sync with the discovery manager so // we wait until the config is fully loaded. select { case <-reloadReady.C: break } err := scrapeManager.Run(discoveryManagerScrape.SyncCh()) level.Info(logger).Log("msg", "Scrape manager stopped") return err }, func(err error) { // Scrape manager needs to be stopped before closing the local TSDB // so that it doesn't try to write samples to a closed storage. level.Info(logger).Log("msg", "Stopping scrape manager...") scrapeManager.Stop() }, ) } { // Make sure that sighup handler is registered with a redirect to the channel before the potentially // long and synchronous tsdb init. hup := make(chan os.Signal) signal.Notify(hup, syscall.SIGHUP) cancel := make(chan struct{}) g.Add( func() error { select { case <-reloadReady.C: break } for { select { case <-hup: if err := reloadConfig(cfg.configFile, logger, reloaders...); err != nil { level.Error(logger).Log("msg", "Error reloading config", "err", err) } case rc := <-webHandler.Reload(): if err := reloadConfig(cfg.configFile, logger, reloaders...); err != nil { level.Error(logger).Log("msg", "Error reloading config", "err", err) rc <- err } else { rc <- nil } case <-cancel: return nil } } }, func(err error) { close(cancel) }, ) } { cancel := make(chan struct{}) g.Add( func() error { select { case <-dbOpen: break // In case a shutdown is initiated before the dbOpen is released case <-cancel: reloadReady.Close() return nil } if err := reloadConfig(cfg.configFile, logger, reloaders...); err != nil { return fmt.Errorf("Error loading config %s", err) } reloadReady.Close() webHandler.Ready() level.Info(logger).Log("msg", "Server is ready to receive web requests.") <-cancel return nil }, func(err error) { close(cancel) }, ) } { cancel := make(chan struct{}) g.Add( func() error { level.Info(logger).Log("msg", "Starting TSDB ...") db, err := tsdb.Open( cfg.localStoragePath, log.With(logger, "component", "tsdb"), prometheus.DefaultRegisterer, &cfg.tsdb, ) if err != nil { return fmt.Errorf("Opening storage failed %s", err) } level.Info(logger).Log("msg", "TSDB started") startTimeMargin := int64(2 * time.Duration(cfg.tsdb.MinBlockDuration).Seconds() * 1000) localStorage.Set(db, startTimeMargin) close(dbOpen) <-cancel return nil }, func(err error) { if err := fanoutStorage.Close(); err != nil { level.Error(logger).Log("msg", "Error stopping storage", "err", err) } close(cancel) }, ) } { g.Add( func() error { if err := webHandler.Run(ctxWeb); err != nil { return fmt.Errorf("Error starting web server: %s", err) } return nil }, func(err error) { // Keep this interrupt before the ruleManager.Stop(). // Shutting down the query engine before the rule manager will cause pending queries // to be canceled and ensures a quick shutdown of the rule manager. cancelWeb() }, ) } { // TODO(krasi) refactor ruleManager.Run() to be blocking to avoid using an extra blocking channel. cancel := make(chan struct{}) g.Add( func() error { ruleManager.Run() <-cancel return nil }, func(err error) { ruleManager.Stop() close(cancel) }, ) } { // Calling notifier.Stop() before ruleManager.Stop() will cause a panic if the ruleManager isn't running, // so keep this interrupt after the ruleManager.Stop(). g.Add( func() error { // When the notifier manager receives a new targets list // it needs to read a valid config for each job. // It depends on the config being in sync with the discovery manager // so we wait until the config is fully loaded. select { case <-reloadReady.C: break } notifier.Run(discoveryManagerNotify.SyncCh()) level.Info(logger).Log("msg", "Notifier manager stopped") return nil }, func(err error) { notifier.Stop() }, ) } if err := g.Run(); err != nil { level.Error(logger).Log("err", err) } level.Info(logger).Log("msg", "See you next time!") } func reloadConfig(filename string, logger log.Logger, rls ...func(*config.Config) error) (err error) { level.Info(logger).Log("msg", "Loading configuration file", "filename", filename) defer func() { if err == nil { configSuccess.Set(1) configSuccessTime.Set(float64(time.Now().Unix())) } else { configSuccess.Set(0) } }() conf, err := config.LoadFile(filename) if err != nil { return fmt.Errorf("couldn't load configuration (--config.file=%s): %v", filename, err) } failed := false for _, rl := range rls { if err := rl(conf); err != nil { level.Error(logger).Log("msg", "Failed to apply configuration", "err", err) failed = true } } if failed { return fmt.Errorf("one or more errors occurred while applying the new configuration (--config.file=%s)", filename) } return nil } func startsOrEndsWithQuote(s string) bool { return strings.HasPrefix(s, "\"") || strings.HasPrefix(s, "'") || strings.HasSuffix(s, "\"") || strings.HasSuffix(s, "'") } // computeExternalURL computes a sanitized external URL from a raw input. It infers unset // URL parts from the OS and the given listen address. func computeExternalURL(u, listenAddr string) (*url.URL, error) { if u == "" { hostname, err := os.Hostname() if err != nil { return nil, err } _, port, err := net.SplitHostPort(listenAddr) if err != nil { return nil, err } u = fmt.Sprintf("http://%s:%s/", hostname, port) } if startsOrEndsWithQuote(u) { return nil, fmt.Errorf("URL must not begin or end with quotes") } eu, err := url.Parse(u) if err != nil { return nil, err } ppref := strings.TrimRight(eu.Path, "/") if ppref != "" && !strings.HasPrefix(ppref, "/") { ppref = "/" + ppref } eu.Path = ppref return eu, nil } // sendAlerts implements a the rules.NotifyFunc for a Notifier. // It filters any non-firing alerts from the input. func sendAlerts(n *notifier.Notifier, externalURL string) rules.NotifyFunc { return func(ctx context.Context, expr string, alerts ...*rules.Alert) error { var res []*notifier.Alert for _, alert := range alerts { // Only send actually firing alerts. if alert.State == rules.StatePending { continue } a := ¬ifier.Alert{ StartsAt: alert.FiredAt, Labels: alert.Labels, Annotations: alert.Annotations, GeneratorURL: externalURL + strutil.TableLinkForExpression(expr), } if !alert.ResolvedAt.IsZero() { a.EndsAt = alert.ResolvedAt } res = append(res, a) } if len(alerts) > 0 { n.Send(res...) } return nil } } prometheus-2.1.0+ds/cmd/prometheus/main_test.go000066400000000000000000000073601323116307200215630ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "flag" "fmt" "net/http" "os" "os/exec" "path/filepath" "testing" "time" "github.com/prometheus/prometheus/util/testutil" ) var promPath string var promConfig = filepath.Join("..", "..", "documentation", "examples", "prometheus.yml") var promData = filepath.Join(os.TempDir(), "data") func TestMain(m *testing.M) { flag.Parse() if testing.Short() { os.Exit(m.Run()) } // On linux with a global proxy the tests will fail as the go client(http,grpc) tries to connect through the proxy. os.Setenv("no_proxy", "localhost,127.0.0.1,0.0.0.0,:") var err error promPath, err = os.Getwd() if err != nil { fmt.Printf("can't get current dir :%s \n", err) os.Exit(1) } promPath = filepath.Join(promPath, "prometheus") build := exec.Command("go", "build", "-o", promPath) output, err := build.CombinedOutput() if err != nil { fmt.Printf("compilation error :%s \n", output) os.Exit(1) } exitCode := m.Run() os.Remove(promPath) os.RemoveAll(promData) os.Exit(exitCode) } // As soon as prometheus starts responding to http request should be able to accept Interrupt signals for a gracefull shutdown. func TestStartupInterrupt(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } prom := exec.Command(promPath, "--config.file="+promConfig, "--storage.tsdb.path="+promData) err := prom.Start() if err != nil { t.Errorf("execution error: %v", err) return } done := make(chan error) go func() { done <- prom.Wait() }() var startedOk bool var stoppedErr error Loop: for x := 0; x < 10; x++ { // error=nil means prometheus has started so can send the interrupt signal and wait for the grace shutdown. if _, err := http.Get("http://localhost:9090/graph"); err == nil { startedOk = true prom.Process.Signal(os.Interrupt) select { case stoppedErr = <-done: break Loop case <-time.After(10 * time.Second): } break Loop } time.Sleep(500 * time.Millisecond) } if !startedOk { t.Errorf("prometheus didn't start in the specified timeout") return } if err := prom.Process.Kill(); err == nil { t.Errorf("prometheus didn't shutdown gracefully after sending the Interrupt signal") } else if stoppedErr != nil && stoppedErr.Error() != "signal: interrupt" { // TODO - find a better way to detect when the process didn't exit as expected! t.Errorf("prometheus exited with an unexpected error:%v", stoppedErr) } } func TestComputeExternalURL(t *testing.T) { tests := []struct { input string valid bool }{ { input: "", valid: true, }, { input: "http://proxy.com/prometheus", valid: true, }, { input: "'https://url/prometheus'", valid: false, }, { input: "'relative/path/with/quotes'", valid: false, }, { input: "http://alertmanager.company.com", valid: true, }, { input: "https://double--dash.de", valid: true, }, { input: "'http://starts/with/quote", valid: false, }, { input: "ends/with/quote\"", valid: false, }, } for _, test := range tests { _, err := computeExternalURL(test.input, "0.0.0.0:9090") if test.valid { testutil.Ok(t, err) } else { testutil.NotOk(t, err, "input=%q", test.input) } } } prometheus-2.1.0+ds/cmd/prometheus/uname_default.go000066400000000000000000000013501323116307200224020ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !linux package main import "runtime" // Uname for any platform other than linux. func Uname() string { return "(" + runtime.GOOS + ")" } prometheus-2.1.0+ds/cmd/prometheus/uname_linux.go000066400000000000000000000021041323116307200221130ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "log" "syscall" ) // Uname returns the uname of the host machine. func Uname() string { buf := syscall.Utsname{} err := syscall.Uname(&buf) if err != nil { log.Fatal("Error!") } str := "(" + charsToString(buf.Sysname[:]) str += " " + charsToString(buf.Release[:]) str += " " + charsToString(buf.Version[:]) str += " " + charsToString(buf.Machine[:]) str += " " + charsToString(buf.Nodename[:]) str += " " + charsToString(buf.Domainname[:]) + ")" return str } prometheus-2.1.0+ds/cmd/prometheus/uname_linux_int8.go000066400000000000000000000015251323116307200230630ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build 386 amd64 arm64 mips64 mips64le mips mipsle // +build linux package main func charsToString(ca []int8) string { s := make([]byte, 0, len(ca)) for _, c := range ca { if byte(c) == 0 { break } s = append(s, byte(c)) } return string(s) } prometheus-2.1.0+ds/cmd/prometheus/uname_linux_uint8.go000066400000000000000000000015021323116307200232430ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build arm ppc64 ppc64le s390x // +build linux package main func charsToString(ca []uint8) string { s := make([]byte, 0, len(ca)) for _, c := range ca { if byte(c) == 0 { break } s = append(s, byte(c)) } return string(s) } prometheus-2.1.0+ds/cmd/promtool/000077500000000000000000000000001323116307200167235ustar00rootroot00000000000000prometheus-2.1.0+ds/cmd/promtool/main.go000066400000000000000000000200021323116307200201700ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "gopkg.in/alecthomas/kingpin.v2" "gopkg.in/yaml.v2" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/common/version" "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/pkg/rulefmt" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/util/promlint" ) func main() { app := kingpin.New(filepath.Base(os.Args[0]), "Tooling for the Prometheus monitoring system.") app.Version(version.Print("promtool")) app.HelpFlag.Short('h') checkCmd := app.Command("check", "Check the resources for validity.") checkConfigCmd := checkCmd.Command("config", "Check if the config files are valid or not.") configFiles := checkConfigCmd.Arg( "config-files", "The config files to check.", ).Required().ExistingFiles() checkRulesCmd := checkCmd.Command("rules", "Check if the rule files are valid or not.") ruleFiles := checkRulesCmd.Arg( "rule-files", "The rule files to check.", ).Required().ExistingFiles() checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage) updateCmd := app.Command("update", "Update the resources to newer formats.") updateRulesCmd := updateCmd.Command("rules", "Update rules from the 1.x to 2.x format.") ruleFilesUp := updateRulesCmd.Arg("rule-files", "The rule files to update.").Required().ExistingFiles() switch kingpin.MustParse(app.Parse(os.Args[1:])) { case checkConfigCmd.FullCommand(): os.Exit(CheckConfig(*configFiles...)) case checkRulesCmd.FullCommand(): os.Exit(CheckRules(*ruleFiles...)) case checkMetricsCmd.FullCommand(): os.Exit(CheckMetrics()) case updateRulesCmd.FullCommand(): os.Exit(UpdateRules(*ruleFilesUp...)) } } // CheckConfig validates configuration files. func CheckConfig(files ...string) int { failed := false for _, f := range files { ruleFiles, err := checkConfig(f) if err != nil { fmt.Fprintln(os.Stderr, " FAILED:", err) failed = true } else { fmt.Printf(" SUCCESS: %d rule files found\n", len(ruleFiles)) } fmt.Println() for _, rf := range ruleFiles { if n, err := checkRules(rf); err != nil { fmt.Fprintln(os.Stderr, " FAILED:", err) failed = true } else { fmt.Printf(" SUCCESS: %d rules found\n", n) } fmt.Println() } } if failed { return 1 } return 0 } func checkFileExists(fn string) error { // Nothing set, nothing to error on. if fn == "" { return nil } _, err := os.Stat(fn) return err } func checkConfig(filename string) ([]string, error) { fmt.Println("Checking", filename) cfg, err := config.LoadFile(filename) if err != nil { return nil, err } var ruleFiles []string for _, rf := range cfg.RuleFiles { rfs, err := filepath.Glob(rf) if err != nil { return nil, err } // If an explicit file was given, error if it is not accessible. if !strings.Contains(rf, "*") { if len(rfs) == 0 { return nil, fmt.Errorf("%q does not point to an existing file", rf) } if err := checkFileExists(rfs[0]); err != nil { return nil, fmt.Errorf("error checking rule file %q: %s", rfs[0], err) } } ruleFiles = append(ruleFiles, rfs...) } for _, scfg := range cfg.ScrapeConfigs { if err := checkFileExists(scfg.HTTPClientConfig.BearerTokenFile); err != nil { return nil, fmt.Errorf("error checking bearer token file %q: %s", scfg.HTTPClientConfig.BearerTokenFile, err) } if err := checkTLSConfig(scfg.HTTPClientConfig.TLSConfig); err != nil { return nil, err } for _, kd := range scfg.ServiceDiscoveryConfig.KubernetesSDConfigs { if err := checkTLSConfig(kd.TLSConfig); err != nil { return nil, err } } for _, filesd := range scfg.ServiceDiscoveryConfig.FileSDConfigs { for _, file := range filesd.Files { files, err := filepath.Glob(file) if err != nil { return nil, err } if len(files) != 0 { // There was at least one match for the glob and we can assume checkFileExists // for all matches would pass, we can continue the loop. continue } fmt.Printf(" WARNING: file %q for file_sd in scrape job %q does not exist\n", file, scfg.JobName) } } } return ruleFiles, nil } func checkTLSConfig(tlsConfig config_util.TLSConfig) error { if err := checkFileExists(tlsConfig.CertFile); err != nil { return fmt.Errorf("error checking client cert file %q: %s", tlsConfig.CertFile, err) } if err := checkFileExists(tlsConfig.KeyFile); err != nil { return fmt.Errorf("error checking client key file %q: %s", tlsConfig.KeyFile, err) } if len(tlsConfig.CertFile) > 0 && len(tlsConfig.KeyFile) == 0 { return fmt.Errorf("client cert file %q specified without client key file", tlsConfig.CertFile) } if len(tlsConfig.KeyFile) > 0 && len(tlsConfig.CertFile) == 0 { return fmt.Errorf("client key file %q specified without client cert file", tlsConfig.KeyFile) } return nil } // CheckRules validates rule files. func CheckRules(files ...string) int { failed := false for _, f := range files { if n, errs := checkRules(f); errs != nil { fmt.Fprintln(os.Stderr, " FAILED:") for _, e := range errs { fmt.Fprintln(os.Stderr, e.Error()) } failed = true } else { fmt.Printf(" SUCCESS: %d rules found\n", n) } fmt.Println() } if failed { return 1 } return 0 } func checkRules(filename string) (int, []error) { fmt.Println("Checking", filename) rgs, errs := rulefmt.ParseFile(filename) if errs != nil { return 0, errs } numRules := 0 for _, rg := range rgs.Groups { numRules += len(rg.Rules) } return numRules, nil } // UpdateRules updates the rule files. func UpdateRules(files ...string) int { failed := false for _, f := range files { if err := updateRules(f); err != nil { fmt.Fprintln(os.Stderr, " FAILED:", err) failed = true } } if failed { return 1 } return 0 } func updateRules(filename string) error { fmt.Println("Updating", filename) content, err := ioutil.ReadFile(filename) if err != nil { return err } rules, err := promql.ParseStmts(string(content)) if err != nil { return err } yamlRG := &rulefmt.RuleGroups{ Groups: []rulefmt.RuleGroup{{ Name: filename, }}, } yamlRules := make([]rulefmt.Rule, 0, len(rules)) for _, rule := range rules { switch r := rule.(type) { case *promql.AlertStmt: yamlRules = append(yamlRules, rulefmt.Rule{ Alert: r.Name, Expr: r.Expr.String(), For: model.Duration(r.Duration), Labels: r.Labels.Map(), Annotations: r.Annotations.Map(), }) case *promql.RecordStmt: yamlRules = append(yamlRules, rulefmt.Rule{ Record: r.Name, Expr: r.Expr.String(), Labels: r.Labels.Map(), }) default: panic("unknown statement type") } } yamlRG.Groups[0].Rules = yamlRules y, err := yaml.Marshal(yamlRG) if err != nil { return err } return ioutil.WriteFile(filename+".yml", y, 0666) } var checkMetricsUsage = strings.TrimSpace(` Pass Prometheus metrics over stdin to lint them for consistency and correctness. examples: $ cat metrics.prom | promtool check metrics $ curl -s http://localhost:9090/metrics | promtool check metrics `) // CheckMetrics performs a linting pass on input metrics. func CheckMetrics() int { l := promlint.New(os.Stdin) problems, err := l.Lint() if err != nil { fmt.Fprintln(os.Stderr, "error while linting:", err) return 1 } for _, p := range problems { fmt.Fprintln(os.Stderr, p.Metric, p.Text) } if len(problems) > 0 { return 3 } return 0 } prometheus-2.1.0+ds/code-of-conduct.md000066400000000000000000000002331323116307200175760ustar00rootroot00000000000000## Prometheus Community Code of Conduct Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). prometheus-2.1.0+ds/config/000077500000000000000000000000001323116307200155525ustar00rootroot00000000000000prometheus-2.1.0+ds/config/config.go000066400000000000000000000621231323116307200173520ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "io/ioutil" "net/url" "path/filepath" "regexp" "strings" "time" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" sd_config "github.com/prometheus/prometheus/discovery/config" yaml_util "github.com/prometheus/prometheus/util/yaml" "gopkg.in/yaml.v2" ) var ( patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`) relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`) ) // Load parses the YAML input s into a Config. func Load(s string) (*Config, error) { cfg := &Config{} // If the entire config body is empty the UnmarshalYAML method is // never called. We thus have to set the DefaultConfig at the entry // point as well. *cfg = DefaultConfig err := yaml.Unmarshal([]byte(s), cfg) if err != nil { return nil, err } cfg.original = s return cfg, nil } // LoadFile parses the given YAML file into a Config. func LoadFile(filename string) (*Config, error) { content, err := ioutil.ReadFile(filename) if err != nil { return nil, err } cfg, err := Load(string(content)) if err != nil { return nil, fmt.Errorf("parsing YAML file %s: %v", filename, err) } resolveFilepaths(filepath.Dir(filename), cfg) return cfg, nil } // The defaults applied before parsing the respective config sections. var ( // DefaultConfig is the default top-level configuration. DefaultConfig = Config{ GlobalConfig: DefaultGlobalConfig, } // DefaultGlobalConfig is the default global configuration. DefaultGlobalConfig = GlobalConfig{ ScrapeInterval: model.Duration(1 * time.Minute), ScrapeTimeout: model.Duration(10 * time.Second), EvaluationInterval: model.Duration(1 * time.Minute), } // DefaultScrapeConfig is the default scrape configuration. DefaultScrapeConfig = ScrapeConfig{ // ScrapeTimeout and ScrapeInterval default to the // configured globals. MetricsPath: "/metrics", Scheme: "http", HonorLabels: false, } // DefaultAlertmanagerConfig is the default alertmanager configuration. DefaultAlertmanagerConfig = AlertmanagerConfig{ Scheme: "http", Timeout: 10 * time.Second, } // DefaultRelabelConfig is the default Relabel configuration. DefaultRelabelConfig = RelabelConfig{ Action: RelabelReplace, Separator: ";", Regex: MustNewRegexp("(.*)"), Replacement: "$1", } // DefaultRemoteWriteConfig is the default remote write configuration. DefaultRemoteWriteConfig = RemoteWriteConfig{ RemoteTimeout: model.Duration(30 * time.Second), QueueConfig: DefaultQueueConfig, } // DefaultQueueConfig is the default remote queue configuration. DefaultQueueConfig = QueueConfig{ // With a maximum of 1000 shards, assuming an average of 100ms remote write // time and 100 samples per batch, we will be able to push 1M samples/s. MaxShards: 1000, MaxSamplesPerSend: 100, // By default, buffer 1000 batches, which at 100ms per batch is 1:40mins. At // 1000 shards, this will buffer 100M samples total. Capacity: 100 * 1000, BatchSendDeadline: 5 * time.Second, // Max number of times to retry a batch on recoverable errors. MaxRetries: 10, MinBackoff: 30 * time.Millisecond, MaxBackoff: 100 * time.Millisecond, } // DefaultRemoteReadConfig is the default remote read configuration. DefaultRemoteReadConfig = RemoteReadConfig{ RemoteTimeout: model.Duration(1 * time.Minute), } ) // Config is the top-level configuration for Prometheus's config files. type Config struct { GlobalConfig GlobalConfig `yaml:"global"` AlertingConfig AlertingConfig `yaml:"alerting,omitempty"` RuleFiles []string `yaml:"rule_files,omitempty"` ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"` RemoteWriteConfigs []*RemoteWriteConfig `yaml:"remote_write,omitempty"` RemoteReadConfigs []*RemoteReadConfig `yaml:"remote_read,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` // original is the input from which the config was parsed. original string } // resolveFilepaths joins all relative paths in a configuration // with a given base directory. func resolveFilepaths(baseDir string, cfg *Config) { join := func(fp string) string { if len(fp) > 0 && !filepath.IsAbs(fp) { fp = filepath.Join(baseDir, fp) } return fp } for i, rf := range cfg.RuleFiles { cfg.RuleFiles[i] = join(rf) } clientPaths := func(scfg *config_util.HTTPClientConfig) { scfg.BearerTokenFile = join(scfg.BearerTokenFile) scfg.TLSConfig.CAFile = join(scfg.TLSConfig.CAFile) scfg.TLSConfig.CertFile = join(scfg.TLSConfig.CertFile) scfg.TLSConfig.KeyFile = join(scfg.TLSConfig.KeyFile) } sdPaths := func(cfg *sd_config.ServiceDiscoveryConfig) { for _, kcfg := range cfg.KubernetesSDConfigs { kcfg.BearerTokenFile = join(kcfg.BearerTokenFile) kcfg.TLSConfig.CAFile = join(kcfg.TLSConfig.CAFile) kcfg.TLSConfig.CertFile = join(kcfg.TLSConfig.CertFile) kcfg.TLSConfig.KeyFile = join(kcfg.TLSConfig.KeyFile) } for _, mcfg := range cfg.MarathonSDConfigs { mcfg.BearerTokenFile = join(mcfg.BearerTokenFile) mcfg.TLSConfig.CAFile = join(mcfg.TLSConfig.CAFile) mcfg.TLSConfig.CertFile = join(mcfg.TLSConfig.CertFile) mcfg.TLSConfig.KeyFile = join(mcfg.TLSConfig.KeyFile) } for _, consulcfg := range cfg.ConsulSDConfigs { consulcfg.TLSConfig.CAFile = join(consulcfg.TLSConfig.CAFile) consulcfg.TLSConfig.CertFile = join(consulcfg.TLSConfig.CertFile) consulcfg.TLSConfig.KeyFile = join(consulcfg.TLSConfig.KeyFile) } for _, filecfg := range cfg.FileSDConfigs { for i, fn := range filecfg.Files { filecfg.Files[i] = join(fn) } } } for _, cfg := range cfg.ScrapeConfigs { clientPaths(&cfg.HTTPClientConfig) sdPaths(&cfg.ServiceDiscoveryConfig) } for _, cfg := range cfg.AlertingConfig.AlertmanagerConfigs { clientPaths(&cfg.HTTPClientConfig) sdPaths(&cfg.ServiceDiscoveryConfig) } } func (c Config) String() string { b, err := yaml.Marshal(c) if err != nil { return fmt.Sprintf("", err) } return string(b) } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultConfig // We want to set c to the defaults and then overwrite it with the input. // To make unmarshal fill the plain data struct rather than calling UnmarshalYAML // again, we have to hide it using a type indirection. type plain Config if err := unmarshal((*plain)(c)); err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "config"); err != nil { return err } // If a global block was open but empty the default global config is overwritten. // We have to restore it here. if c.GlobalConfig.isZero() { c.GlobalConfig = DefaultGlobalConfig } for _, rf := range c.RuleFiles { if !patRulePath.MatchString(rf) { return fmt.Errorf("invalid rule file path %q", rf) } } // Do global overrides and validate unique names. jobNames := map[string]struct{}{} for _, scfg := range c.ScrapeConfigs { // First set the correct scrape interval, then check that the timeout // (inferred or explicit) is not greater than that. if scfg.ScrapeInterval == 0 { scfg.ScrapeInterval = c.GlobalConfig.ScrapeInterval } if scfg.ScrapeTimeout > scfg.ScrapeInterval { return fmt.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", scfg.JobName) } if scfg.ScrapeTimeout == 0 { if c.GlobalConfig.ScrapeTimeout > scfg.ScrapeInterval { scfg.ScrapeTimeout = scfg.ScrapeInterval } else { scfg.ScrapeTimeout = c.GlobalConfig.ScrapeTimeout } } if _, ok := jobNames[scfg.JobName]; ok { return fmt.Errorf("found multiple scrape configs with job name %q", scfg.JobName) } jobNames[scfg.JobName] = struct{}{} } return nil } // GlobalConfig configures values that are used across other configuration // objects. type GlobalConfig struct { // How frequently to scrape targets by default. ScrapeInterval model.Duration `yaml:"scrape_interval,omitempty"` // The default timeout when scraping targets. ScrapeTimeout model.Duration `yaml:"scrape_timeout,omitempty"` // How frequently to evaluate rules by default. EvaluationInterval model.Duration `yaml:"evaluation_interval,omitempty"` // The labels to add to any timeseries that this Prometheus instance scrapes. ExternalLabels model.LabelSet `yaml:"external_labels,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { // Create a clean global config as the previous one was already populated // by the default due to the YAML parser behavior for empty blocks. gc := &GlobalConfig{} type plain GlobalConfig if err := unmarshal((*plain)(gc)); err != nil { return err } if err := yaml_util.CheckOverflow(gc.XXX, "global config"); err != nil { return err } // First set the correct scrape interval, then check that the timeout // (inferred or explicit) is not greater than that. if gc.ScrapeInterval == 0 { gc.ScrapeInterval = DefaultGlobalConfig.ScrapeInterval } if gc.ScrapeTimeout > gc.ScrapeInterval { return fmt.Errorf("global scrape timeout greater than scrape interval") } if gc.ScrapeTimeout == 0 { if DefaultGlobalConfig.ScrapeTimeout > gc.ScrapeInterval { gc.ScrapeTimeout = gc.ScrapeInterval } else { gc.ScrapeTimeout = DefaultGlobalConfig.ScrapeTimeout } } if gc.EvaluationInterval == 0 { gc.EvaluationInterval = DefaultGlobalConfig.EvaluationInterval } *c = *gc return nil } // isZero returns true iff the global config is the zero value. func (c *GlobalConfig) isZero() bool { return c.ExternalLabels == nil && c.ScrapeInterval == 0 && c.ScrapeTimeout == 0 && c.EvaluationInterval == 0 } // ScrapeConfig configures a scraping unit for Prometheus. type ScrapeConfig struct { // The job name to which the job label is set by default. JobName string `yaml:"job_name"` // Indicator whether the scraped metrics should remain unmodified. HonorLabels bool `yaml:"honor_labels,omitempty"` // A set of query parameters with which the target is scraped. Params url.Values `yaml:"params,omitempty"` // How frequently to scrape the targets of this scrape config. ScrapeInterval model.Duration `yaml:"scrape_interval,omitempty"` // The timeout for scraping targets of this config. ScrapeTimeout model.Duration `yaml:"scrape_timeout,omitempty"` // The HTTP resource path on which to fetch metrics from targets. MetricsPath string `yaml:"metrics_path,omitempty"` // The URL scheme with which to fetch metrics from targets. Scheme string `yaml:"scheme,omitempty"` // More than this many samples post metric-relabelling will cause the scrape to fail. SampleLimit uint `yaml:"sample_limit,omitempty"` // We cannot do proper Go type embedding below as the parser will then parse // values arbitrarily into the overflow maps of further-down types. ServiceDiscoveryConfig sd_config.ServiceDiscoveryConfig `yaml:",inline"` HTTPClientConfig config_util.HTTPClientConfig `yaml:",inline"` // List of target relabel configurations. RelabelConfigs []*RelabelConfig `yaml:"relabel_configs,omitempty"` // List of metric relabel configurations. MetricRelabelConfigs []*RelabelConfig `yaml:"metric_relabel_configs,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultScrapeConfig type plain ScrapeConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err = yaml_util.CheckOverflow(c.XXX, "scrape_config"); err != nil { return err } if len(c.JobName) == 0 { return fmt.Errorf("job_name is empty") } // The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer. // We cannot make it a pointer as the parser panics for inlined pointer structs. // Thus we just do its validation here. if err = c.HTTPClientConfig.Validate(); err != nil { return err } // Check for users putting URLs in target groups. if len(c.RelabelConfigs) == 0 { for _, tg := range c.ServiceDiscoveryConfig.StaticConfigs { for _, t := range tg.Targets { if err = CheckTargetAddress(t[model.AddressLabel]); err != nil { return err } } } } return nil } // AlertingConfig configures alerting and alertmanager related configs. type AlertingConfig struct { AlertRelabelConfigs []*RelabelConfig `yaml:"alert_relabel_configs,omitempty"` AlertmanagerConfigs []*AlertmanagerConfig `yaml:"alertmanagers,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *AlertingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { // Create a clean global config as the previous one was already populated // by the default due to the YAML parser behavior for empty blocks. *c = AlertingConfig{} type plain AlertingConfig if err := unmarshal((*plain)(c)); err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "alerting config") } // AlertmanagerConfig configures how Alertmanagers can be discovered and communicated with. type AlertmanagerConfig struct { // We cannot do proper Go type embedding below as the parser will then parse // values arbitrarily into the overflow maps of further-down types. ServiceDiscoveryConfig sd_config.ServiceDiscoveryConfig `yaml:",inline"` HTTPClientConfig config_util.HTTPClientConfig `yaml:",inline"` // The URL scheme to use when talking to Alertmanagers. Scheme string `yaml:"scheme,omitempty"` // Path prefix to add in front of the push endpoint path. PathPrefix string `yaml:"path_prefix,omitempty"` // The timeout used when sending alerts. Timeout time.Duration `yaml:"timeout,omitempty"` // List of Alertmanager relabel configurations. RelabelConfigs []*RelabelConfig `yaml:"relabel_configs,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultAlertmanagerConfig type plain AlertmanagerConfig if err := unmarshal((*plain)(c)); err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "alertmanager config"); err != nil { return err } // The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer. // We cannot make it a pointer as the parser panics for inlined pointer structs. // Thus we just do its validation here. if err := c.HTTPClientConfig.Validate(); err != nil { return err } // Check for users putting URLs in target groups. if len(c.RelabelConfigs) == 0 { for _, tg := range c.ServiceDiscoveryConfig.StaticConfigs { for _, t := range tg.Targets { if err := CheckTargetAddress(t[model.AddressLabel]); err != nil { return err } } } } return nil } // CheckTargetAddress checks if target address is valid. func CheckTargetAddress(address model.LabelValue) error { // For now check for a URL, we may want to expand this later. if strings.Contains(string(address), "/") { return fmt.Errorf("%q is not a valid hostname", address) } return nil } // ClientCert contains client cert credentials. type ClientCert struct { Cert string `yaml:"cert"` Key config_util.Secret `yaml:"key"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // FileSDConfig is the configuration for file based discovery. type FileSDConfig struct { Files []string `yaml:"files"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // RelabelAction is the action to be performed on relabeling. type RelabelAction string const ( // RelabelReplace performs a regex replacement. RelabelReplace RelabelAction = "replace" // RelabelKeep drops targets for which the input does not match the regex. RelabelKeep RelabelAction = "keep" // RelabelDrop drops targets for which the input does match the regex. RelabelDrop RelabelAction = "drop" // RelabelHashMod sets a label to the modulus of a hash of labels. RelabelHashMod RelabelAction = "hashmod" // RelabelLabelMap copies labels to other labelnames based on a regex. RelabelLabelMap RelabelAction = "labelmap" // RelabelLabelDrop drops any label matching the regex. RelabelLabelDrop RelabelAction = "labeldrop" // RelabelLabelKeep drops any label not matching the regex. RelabelLabelKeep RelabelAction = "labelkeep" ) // UnmarshalYAML implements the yaml.Unmarshaler interface. func (a *RelabelAction) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err != nil { return err } switch act := RelabelAction(strings.ToLower(s)); act { case RelabelReplace, RelabelKeep, RelabelDrop, RelabelHashMod, RelabelLabelMap, RelabelLabelDrop, RelabelLabelKeep: *a = act return nil } return fmt.Errorf("unknown relabel action %q", s) } // RelabelConfig is the configuration for relabeling of target label sets. type RelabelConfig struct { // A list of labels from which values are taken and concatenated // with the configured separator in order. SourceLabels model.LabelNames `yaml:"source_labels,flow,omitempty"` // Separator is the string between concatenated values from the source labels. Separator string `yaml:"separator,omitempty"` // Regex against which the concatenation is matched. Regex Regexp `yaml:"regex,omitempty"` // Modulus to take of the hash of concatenated values from the source labels. Modulus uint64 `yaml:"modulus,omitempty"` // TargetLabel is the label to which the resulting string is written in a replacement. // Regexp interpolation is allowed for the replace action. TargetLabel string `yaml:"target_label,omitempty"` // Replacement is the regex replacement pattern to be used. Replacement string `yaml:"replacement,omitempty"` // Action is the action to be performed for the relabeling. Action RelabelAction `yaml:"action,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *RelabelConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultRelabelConfig type plain RelabelConfig if err := unmarshal((*plain)(c)); err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "relabel_config"); err != nil { return err } if c.Regex.Regexp == nil { c.Regex = MustNewRegexp("") } if c.Modulus == 0 && c.Action == RelabelHashMod { return fmt.Errorf("relabel configuration for hashmod requires non-zero modulus") } if (c.Action == RelabelReplace || c.Action == RelabelHashMod) && c.TargetLabel == "" { return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action) } if c.Action == RelabelReplace && !relabelTarget.MatchString(c.TargetLabel) { return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action) } if c.Action == RelabelHashMod && !model.LabelName(c.TargetLabel).IsValid() { return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action) } if c.Action == RelabelLabelDrop || c.Action == RelabelLabelKeep { if c.SourceLabels != nil || c.TargetLabel != DefaultRelabelConfig.TargetLabel || c.Modulus != DefaultRelabelConfig.Modulus || c.Separator != DefaultRelabelConfig.Separator || c.Replacement != DefaultRelabelConfig.Replacement { return fmt.Errorf("%s action requires only 'regex', and no other fields", c.Action) } } return nil } // Regexp encapsulates a regexp.Regexp and makes it YAML marshallable. type Regexp struct { *regexp.Regexp original string } // NewRegexp creates a new anchored Regexp and returns an error if the // passed-in regular expression does not compile. func NewRegexp(s string) (Regexp, error) { regex, err := regexp.Compile("^(?:" + s + ")$") return Regexp{ Regexp: regex, original: s, }, err } // MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile. func MustNewRegexp(s string) Regexp { re, err := NewRegexp(s) if err != nil { panic(err) } return re } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err != nil { return err } r, err := NewRegexp(s) if err != nil { return err } *re = r return nil } // MarshalYAML implements the yaml.Marshaler interface. func (re Regexp) MarshalYAML() (interface{}, error) { if re.original != "" { return re.original, nil } return nil, nil } // RemoteWriteConfig is the configuration for writing to remote storage. type RemoteWriteConfig struct { URL *config_util.URL `yaml:"url"` RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"` WriteRelabelConfigs []*RelabelConfig `yaml:"write_relabel_configs,omitempty"` // We cannot do proper Go type embedding below as the parser will then parse // values arbitrarily into the overflow maps of further-down types. HTTPClientConfig config_util.HTTPClientConfig `yaml:",inline"` QueueConfig QueueConfig `yaml:"queue_config,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultRemoteWriteConfig type plain RemoteWriteConfig if err := unmarshal((*plain)(c)); err != nil { return err } if c.URL == nil { return fmt.Errorf("url for remote_write is empty") } // The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer. // We cannot make it a pointer as the parser panics for inlined pointer structs. // Thus we just do its validation here. if err := c.HTTPClientConfig.Validate(); err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "remote_write") } // QueueConfig is the configuration for the queue used to write to remote // storage. type QueueConfig struct { // Number of samples to buffer per shard before we start dropping them. Capacity int `yaml:"capacity,omitempty"` // Max number of shards, i.e. amount of concurrency. MaxShards int `yaml:"max_shards,omitempty"` // Maximum number of samples per send. MaxSamplesPerSend int `yaml:"max_samples_per_send,omitempty"` // Maximum time sample will wait in buffer. BatchSendDeadline time.Duration `yaml:"batch_send_deadline,omitempty"` // Max number of times to retry a batch on recoverable errors. MaxRetries int `yaml:"max_retries,omitempty"` // On recoverable errors, backoff exponentially. MinBackoff time.Duration `yaml:"min_backoff,omitempty"` MaxBackoff time.Duration `yaml:"max_backoff,omitempty"` } // RemoteReadConfig is the configuration for reading from remote storage. type RemoteReadConfig struct { URL *config_util.URL `yaml:"url"` RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"` ReadRecent bool `yaml:"read_recent,omitempty"` // We cannot do proper Go type embedding below as the parser will then parse // values arbitrarily into the overflow maps of further-down types. HTTPClientConfig config_util.HTTPClientConfig `yaml:",inline"` // RequiredMatchers is an optional list of equality matchers which have to // be present in a selector to query the remote read endpoint. RequiredMatchers model.LabelSet `yaml:"required_matchers,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *RemoteReadConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultRemoteReadConfig type plain RemoteReadConfig if err := unmarshal((*plain)(c)); err != nil { return err } if c.URL == nil { return fmt.Errorf("url for remote_read is empty") } // The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer. // We cannot make it a pointer as the parser panics for inlined pointer structs. // Thus we just do its validation here. if err := c.HTTPClientConfig.Validate(); err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "remote_read") } prometheus-2.1.0+ds/config/config_default_test.go000066400000000000000000000015771323116307200221230ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows package config const ruleFilesConfigFile = "testdata/rules_abs_path.good.yml" var ruleFilesExpectedConf = &Config{ GlobalConfig: DefaultGlobalConfig, RuleFiles: []string{ "testdata/first.rules", "testdata/rules/second.rules", "/absolute/third.rules", }, original: "", } prometheus-2.1.0+ds/config/config_test.go000066400000000000000000000516541323116307200204200ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "encoding/json" "io/ioutil" "net/url" "path/filepath" "regexp" "strings" "testing" "time" "github.com/prometheus/prometheus/discovery/azure" "github.com/prometheus/prometheus/discovery/consul" "github.com/prometheus/prometheus/discovery/dns" "github.com/prometheus/prometheus/discovery/ec2" "github.com/prometheus/prometheus/discovery/file" "github.com/prometheus/prometheus/discovery/kubernetes" "github.com/prometheus/prometheus/discovery/marathon" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/discovery/triton" "github.com/prometheus/prometheus/discovery/zookeeper" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" sd_config "github.com/prometheus/prometheus/discovery/config" "github.com/prometheus/prometheus/util/testutil" "gopkg.in/yaml.v2" ) func mustParseURL(u string) *config_util.URL { parsed, err := url.Parse(u) if err != nil { panic(err) } return &config_util.URL{URL: parsed} } var expectedConf = &Config{ GlobalConfig: GlobalConfig{ ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, EvaluationInterval: model.Duration(30 * time.Second), ExternalLabels: model.LabelSet{ "monitor": "codelab", "foo": "bar", }, }, RuleFiles: []string{ filepath.FromSlash("testdata/first.rules"), filepath.FromSlash("testdata/my/*.rules"), }, RemoteWriteConfigs: []*RemoteWriteConfig{ { URL: mustParseURL("http://remote1/push"), RemoteTimeout: model.Duration(30 * time.Second), WriteRelabelConfigs: []*RelabelConfig{ { SourceLabels: model.LabelNames{"__name__"}, Separator: ";", Regex: MustNewRegexp("expensive.*"), Replacement: "$1", Action: RelabelDrop, }, }, QueueConfig: DefaultQueueConfig, }, { URL: mustParseURL("http://remote2/push"), RemoteTimeout: model.Duration(30 * time.Second), QueueConfig: DefaultQueueConfig, }, }, RemoteReadConfigs: []*RemoteReadConfig{ { URL: mustParseURL("http://remote1/read"), RemoteTimeout: model.Duration(1 * time.Minute), ReadRecent: true, }, { URL: mustParseURL("http://remote3/read"), RemoteTimeout: model.Duration(1 * time.Minute), ReadRecent: false, RequiredMatchers: model.LabelSet{"job": "special"}, }, }, ScrapeConfigs: []*ScrapeConfig{ { JobName: "prometheus", HonorLabels: true, ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, HTTPClientConfig: config_util.HTTPClientConfig{ BearerTokenFile: filepath.FromSlash("testdata/valid_token_file"), }, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ StaticConfigs: []*targetgroup.Group{ { Targets: []model.LabelSet{ {model.AddressLabel: "localhost:9090"}, {model.AddressLabel: "localhost:9191"}, }, Labels: model.LabelSet{ "my": "label", "your": "label", }, }, }, FileSDConfigs: []*file.SDConfig{ { Files: []string{"testdata/foo/*.slow.json", "testdata/foo/*.slow.yml", "testdata/single/file.yml"}, RefreshInterval: model.Duration(10 * time.Minute), }, { Files: []string{"testdata/bar/*.yaml"}, RefreshInterval: model.Duration(5 * time.Minute), }, }, }, RelabelConfigs: []*RelabelConfig{ { SourceLabels: model.LabelNames{"job", "__meta_dns_name"}, TargetLabel: "job", Separator: ";", Regex: MustNewRegexp("(.*)some-[regex]"), Replacement: "foo-${1}", Action: RelabelReplace, }, { SourceLabels: model.LabelNames{"abc"}, TargetLabel: "cde", Separator: ";", Regex: DefaultRelabelConfig.Regex, Replacement: DefaultRelabelConfig.Replacement, Action: RelabelReplace, }, { TargetLabel: "abc", Separator: ";", Regex: DefaultRelabelConfig.Regex, Replacement: "static", Action: RelabelReplace, }, { TargetLabel: "abc", Separator: ";", Regex: MustNewRegexp(""), Replacement: "static", Action: RelabelReplace, }, }, }, { JobName: "service-x", ScrapeInterval: model.Duration(50 * time.Second), ScrapeTimeout: model.Duration(5 * time.Second), SampleLimit: 1000, HTTPClientConfig: config_util.HTTPClientConfig{ BasicAuth: &config_util.BasicAuth{ Username: "admin_name", Password: "multiline\nmysecret\ntest", }, }, MetricsPath: "/my_path", Scheme: "https", ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ DNSSDConfigs: []*dns.SDConfig{ { Names: []string{ "first.dns.address.domain.com", "second.dns.address.domain.com", }, RefreshInterval: model.Duration(15 * time.Second), Type: "SRV", }, { Names: []string{ "first.dns.address.domain.com", }, RefreshInterval: model.Duration(30 * time.Second), Type: "SRV", }, }, }, RelabelConfigs: []*RelabelConfig{ { SourceLabels: model.LabelNames{"job"}, Regex: MustNewRegexp("(.*)some-[regex]"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelDrop, }, { SourceLabels: model.LabelNames{"__address__"}, TargetLabel: "__tmp_hash", Regex: DefaultRelabelConfig.Regex, Replacement: DefaultRelabelConfig.Replacement, Modulus: 8, Separator: ";", Action: RelabelHashMod, }, { SourceLabels: model.LabelNames{"__tmp_hash"}, Regex: MustNewRegexp("1"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelKeep, }, { Regex: MustNewRegexp("1"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelLabelMap, }, { Regex: MustNewRegexp("d"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelLabelDrop, }, { Regex: MustNewRegexp("k"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelLabelKeep, }, }, MetricRelabelConfigs: []*RelabelConfig{ { SourceLabels: model.LabelNames{"__name__"}, Regex: MustNewRegexp("expensive_metric.*"), Separator: ";", Replacement: DefaultRelabelConfig.Replacement, Action: RelabelDrop, }, }, }, { JobName: "service-y", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ ConsulSDConfigs: []*consul.SDConfig{ { Server: "localhost:1234", Token: "mysecret", Services: []string{"nginx", "cache", "mysql"}, TagSeparator: consul.DefaultSDConfig.TagSeparator, Scheme: "https", TLSConfig: config_util.TLSConfig{ CertFile: filepath.FromSlash("testdata/valid_cert_file"), KeyFile: filepath.FromSlash("testdata/valid_key_file"), CAFile: filepath.FromSlash("testdata/valid_ca_file"), InsecureSkipVerify: false, }, }, }, }, RelabelConfigs: []*RelabelConfig{ { SourceLabels: model.LabelNames{"__meta_sd_consul_tags"}, Regex: MustNewRegexp("label:([^=]+)=([^,]+)"), Separator: ",", TargetLabel: "${1}", Replacement: "${2}", Action: RelabelReplace, }, }, }, { JobName: "service-z", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: model.Duration(10 * time.Second), MetricsPath: "/metrics", Scheme: "http", HTTPClientConfig: config_util.HTTPClientConfig{ TLSConfig: config_util.TLSConfig{ CertFile: filepath.FromSlash("testdata/valid_cert_file"), KeyFile: filepath.FromSlash("testdata/valid_key_file"), }, BearerToken: "mysecret", }, }, { JobName: "service-kubernetes", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ KubernetesSDConfigs: []*kubernetes.SDConfig{ { APIServer: kubernetesSDHostURL(), Role: kubernetes.RoleEndpoint, BasicAuth: &config_util.BasicAuth{ Username: "myusername", Password: "mysecret", }, NamespaceDiscovery: kubernetes.NamespaceDiscovery{}, }, }, }, }, { JobName: "service-kubernetes-namespaces", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ KubernetesSDConfigs: []*kubernetes.SDConfig{ { APIServer: kubernetesSDHostURL(), Role: kubernetes.RoleEndpoint, NamespaceDiscovery: kubernetes.NamespaceDiscovery{ Names: []string{ "default", }, }, }, }, }, }, { JobName: "service-marathon", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ MarathonSDConfigs: []*marathon.SDConfig{ { Servers: []string{ "https://marathon.example.com:443", }, Timeout: model.Duration(30 * time.Second), RefreshInterval: model.Duration(30 * time.Second), TLSConfig: config_util.TLSConfig{ CertFile: filepath.FromSlash("testdata/valid_cert_file"), KeyFile: filepath.FromSlash("testdata/valid_key_file"), }, }, }, }, }, { JobName: "service-ec2", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ EC2SDConfigs: []*ec2.SDConfig{ { Region: "us-east-1", AccessKey: "access", SecretKey: "mysecret", Profile: "profile", RefreshInterval: model.Duration(60 * time.Second), Port: 80, }, }, }, }, { JobName: "service-azure", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ AzureSDConfigs: []*azure.SDConfig{ { SubscriptionID: "11AAAA11-A11A-111A-A111-1111A1111A11", TenantID: "BBBB222B-B2B2-2B22-B222-2BB2222BB2B2", ClientID: "333333CC-3C33-3333-CCC3-33C3CCCCC33C", ClientSecret: "mysecret", RefreshInterval: model.Duration(5 * time.Minute), Port: 9100, }, }, }, }, { JobName: "service-nerve", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ NerveSDConfigs: []*zookeeper.NerveSDConfig{ { Servers: []string{"localhost"}, Paths: []string{"/monitoring"}, Timeout: model.Duration(10 * time.Second), }, }, }, }, { JobName: "0123service-xxx", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ StaticConfigs: []*targetgroup.Group{ { Targets: []model.LabelSet{ {model.AddressLabel: "localhost:9090"}, }, }, }, }, }, { JobName: "測試", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ StaticConfigs: []*targetgroup.Group{ { Targets: []model.LabelSet{ {model.AddressLabel: "localhost:9090"}, }, }, }, }, }, { JobName: "service-triton", ScrapeInterval: model.Duration(15 * time.Second), ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, MetricsPath: DefaultScrapeConfig.MetricsPath, Scheme: DefaultScrapeConfig.Scheme, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ TritonSDConfigs: []*triton.SDConfig{ { Account: "testAccount", DNSSuffix: "triton.example.com", Endpoint: "triton.example.com", Port: 9163, RefreshInterval: model.Duration(60 * time.Second), Version: 1, TLSConfig: config_util.TLSConfig{ CertFile: "testdata/valid_cert_file", KeyFile: "testdata/valid_key_file", }, }, }, }, }, }, AlertingConfig: AlertingConfig{ AlertmanagerConfigs: []*AlertmanagerConfig{ { Scheme: "https", Timeout: 10 * time.Second, ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ StaticConfigs: []*targetgroup.Group{ { Targets: []model.LabelSet{ {model.AddressLabel: "1.2.3.4:9093"}, {model.AddressLabel: "1.2.3.5:9093"}, {model.AddressLabel: "1.2.3.6:9093"}, }, }, }, }, }, }, }, original: "", } func TestLoadConfig(t *testing.T) { // Parse a valid file that sets a global scrape timeout. This tests whether parsing // an overwritten default field in the global config permanently changes the default. _, err := LoadFile("testdata/global_timeout.good.yml") testutil.Ok(t, err) c, err := LoadFile("testdata/conf.good.yml") testutil.Ok(t, err) expectedConf.original = c.original testutil.Equals(t, expectedConf, c) } // YAML marshalling must not reveal authentication credentials. func TestElideSecrets(t *testing.T) { c, err := LoadFile("testdata/conf.good.yml") testutil.Ok(t, err) secretRe := regexp.MustCompile(`\\u003csecret\\u003e|`) config, err := yaml.Marshal(c) testutil.Ok(t, err) yamlConfig := string(config) matches := secretRe.FindAllStringIndex(yamlConfig, -1) testutil.Assert(t, len(matches) == 6, "wrong number of secret matches found") testutil.Assert(t, !strings.Contains(yamlConfig, "mysecret"), "yaml marshal reveals authentication credentials.") } func TestLoadConfigRuleFilesAbsolutePath(t *testing.T) { // Parse a valid file that sets a rule files with an absolute path c, err := LoadFile(ruleFilesConfigFile) testutil.Ok(t, err) ruleFilesExpectedConf.original = c.original testutil.Equals(t, ruleFilesExpectedConf, c) } var expectedErrors = []struct { filename string errMsg string }{ { filename: "jobname.bad.yml", errMsg: `job_name is empty`, }, { filename: "jobname_dup.bad.yml", errMsg: `found multiple scrape configs with job name "prometheus"`, }, { filename: "scrape_interval.bad.yml", errMsg: `scrape timeout greater than scrape interval`, }, { filename: "labelname.bad.yml", errMsg: `"not$allowed" is not a valid label name`, }, { filename: "labelname2.bad.yml", errMsg: `"not:allowed" is not a valid label name`, }, { filename: "regex.bad.yml", errMsg: "error parsing regexp", }, { filename: "modulus_missing.bad.yml", errMsg: "relabel configuration for hashmod requires non-zero modulus", }, { filename: "labelkeep.bad.yml", errMsg: "labelkeep action requires only 'regex', and no other fields", }, { filename: "labelkeep2.bad.yml", errMsg: "labelkeep action requires only 'regex', and no other fields", }, { filename: "labelkeep3.bad.yml", errMsg: "labelkeep action requires only 'regex', and no other fields", }, { filename: "labelkeep4.bad.yml", errMsg: "labelkeep action requires only 'regex', and no other fields", }, { filename: "labelkeep5.bad.yml", errMsg: "labelkeep action requires only 'regex', and no other fields", }, { filename: "labeldrop.bad.yml", errMsg: "labeldrop action requires only 'regex', and no other fields", }, { filename: "labeldrop2.bad.yml", errMsg: "labeldrop action requires only 'regex', and no other fields", }, { filename: "labeldrop3.bad.yml", errMsg: "labeldrop action requires only 'regex', and no other fields", }, { filename: "labeldrop4.bad.yml", errMsg: "labeldrop action requires only 'regex', and no other fields", }, { filename: "labeldrop5.bad.yml", errMsg: "labeldrop action requires only 'regex', and no other fields", }, { filename: "rules.bad.yml", errMsg: "invalid rule file path", }, { filename: "unknown_attr.bad.yml", errMsg: "unknown fields in scrape_config: consult_sd_configs", }, { filename: "bearertoken.bad.yml", errMsg: "at most one of bearer_token & bearer_token_file must be configured", }, { filename: "bearertoken_basicauth.bad.yml", errMsg: "at most one of basic_auth, bearer_token & bearer_token_file must be configured", }, { filename: "kubernetes_bearertoken.bad.yml", errMsg: "at most one of bearer_token & bearer_token_file must be configured", }, { filename: "kubernetes_role.bad.yml", errMsg: "role", }, { filename: "kubernetes_namespace_discovery.bad.yml", errMsg: "unknown fields in namespaces", }, { filename: "kubernetes_bearertoken_basicauth.bad.yml", errMsg: "at most one of basic_auth, bearer_token & bearer_token_file must be configured", }, { filename: "marathon_no_servers.bad.yml", errMsg: "Marathon SD config must contain at least one Marathon server", }, { filename: "url_in_targetgroup.bad.yml", errMsg: "\"http://bad\" is not a valid hostname", }, { filename: "target_label_missing.bad.yml", errMsg: "relabel configuration for replace action requires 'target_label' value", }, { filename: "target_label_hashmod_missing.bad.yml", errMsg: "relabel configuration for hashmod action requires 'target_label' value", }, { filename: "unknown_global_attr.bad.yml", errMsg: "unknown fields in global config: nonexistent_field", }, { filename: "remote_read_url_missing.bad.yml", errMsg: `url for remote_read is empty`, }, { filename: "remote_write_url_missing.bad.yml", errMsg: `url for remote_write is empty`, }, } func TestBadConfigs(t *testing.T) { for _, ee := range expectedErrors { _, err := LoadFile("testdata/" + ee.filename) testutil.NotOk(t, err, "%s", ee.filename) testutil.Assert(t, strings.Contains(err.Error(), ee.errMsg), "Expected error for %s to contain %q but got: %s", ee.filename, ee.errMsg, err) } } func TestBadStaticConfigs(t *testing.T) { content, err := ioutil.ReadFile("testdata/static_config.bad.json") testutil.Ok(t, err) var tg targetgroup.Group err = json.Unmarshal(content, &tg) testutil.NotOk(t, err, "") } func TestEmptyConfig(t *testing.T) { c, err := Load("") testutil.Ok(t, err) exp := DefaultConfig testutil.Equals(t, exp, *c) } func TestEmptyGlobalBlock(t *testing.T) { c, err := Load("global:\n") testutil.Ok(t, err) exp := DefaultConfig exp.original = "global:\n" testutil.Equals(t, exp, *c) } func TestTargetLabelValidity(t *testing.T) { tests := []struct { str string valid bool }{ {"-label", false}, {"label", true}, {"label${1}", true}, {"${1}label", true}, {"${1}", true}, {"${1}label", true}, {"${", false}, {"$", false}, {"${}", false}, {"foo${", false}, {"$1", true}, {"asd$2asd", true}, {"-foo${1}bar-", false}, {"_${1}_", true}, {"foo${bar}foo", true}, } for _, test := range tests { testutil.Assert(t, relabelTarget.Match([]byte(test.str)) == test.valid, "Expected %q to be %v", test.str, test.valid) } } func kubernetesSDHostURL() config_util.URL { tURL, _ := url.Parse("https://localhost:1234") return config_util.URL{URL: tURL} } prometheus-2.1.0+ds/config/config_windows_test.go000066400000000000000000000015721323116307200221640ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config const ruleFilesConfigFile = "testdata/rules_abs_path_windows.good.yml" var ruleFilesExpectedConf = &Config{ GlobalConfig: DefaultGlobalConfig, RuleFiles: []string{ "testdata\\first.rules", "testdata\\rules\\second.rules", "c:\\absolute\\third.rules", }, original: "", } prometheus-2.1.0+ds/config/testdata/000077500000000000000000000000001323116307200173635ustar00rootroot00000000000000prometheus-2.1.0+ds/config/testdata/bearertoken.bad.yml000066400000000000000000000001421323116307200231310ustar00rootroot00000000000000scrape_configs: - job_name: prometheus bearer_token: 1234 bearer_token_file: somefile prometheus-2.1.0+ds/config/testdata/bearertoken_basicauth.bad.yml000066400000000000000000000002001323116307200251470ustar00rootroot00000000000000scrape_configs: - job_name: prometheus bearer_token: 1234 basic_auth: username: user password: password prometheus-2.1.0+ds/config/testdata/conf.good.yml000066400000000000000000000113341323116307200217640ustar00rootroot00000000000000# my global config global: scrape_interval: 15s evaluation_interval: 30s # scrape_timeout is set to the global default (10s). external_labels: monitor: codelab foo: bar rule_files: - "first.rules" - "my/*.rules" remote_write: - url: http://remote1/push write_relabel_configs: - source_labels: [__name__] regex: expensive.* action: drop - url: http://remote2/push remote_read: - url: http://remote1/read read_recent: true - url: http://remote3/read read_recent: false required_matchers: job: special scrape_configs: - job_name: prometheus honor_labels: true # scrape_interval is defined by the configured global (15s). # scrape_timeout is defined by the global default (10s). # metrics_path defaults to '/metrics' # scheme defaults to 'http'. file_sd_configs: - files: - foo/*.slow.json - foo/*.slow.yml - single/file.yml refresh_interval: 10m - files: - bar/*.yaml static_configs: - targets: ['localhost:9090', 'localhost:9191'] labels: my: label your: label relabel_configs: - source_labels: [job, __meta_dns_name] regex: (.*)some-[regex] target_label: job replacement: foo-${1} # action defaults to 'replace' - source_labels: [abc] target_label: cde - replacement: static target_label: abc - regex: replacement: static target_label: abc bearer_token_file: valid_token_file - job_name: service-x basic_auth: username: admin_name password: "multiline\nmysecret\ntest" scrape_interval: 50s scrape_timeout: 5s sample_limit: 1000 metrics_path: /my_path scheme: https dns_sd_configs: - refresh_interval: 15s names: - first.dns.address.domain.com - second.dns.address.domain.com - names: - first.dns.address.domain.com # refresh_interval defaults to 30s. relabel_configs: - source_labels: [job] regex: (.*)some-[regex] action: drop - source_labels: [__address__] modulus: 8 target_label: __tmp_hash action: hashmod - source_labels: [__tmp_hash] regex: 1 action: keep - action: labelmap regex: 1 - action: labeldrop regex: d - action: labelkeep regex: k metric_relabel_configs: - source_labels: [__name__] regex: expensive_metric.* action: drop - job_name: service-y consul_sd_configs: - server: 'localhost:1234' token: mysecret services: ['nginx', 'cache', 'mysql'] scheme: https tls_config: ca_file: valid_ca_file cert_file: valid_cert_file key_file: valid_key_file insecure_skip_verify: false relabel_configs: - source_labels: [__meta_sd_consul_tags] separator: ',' regex: label:([^=]+)=([^,]+) target_label: ${1} replacement: ${2} - job_name: service-z tls_config: cert_file: valid_cert_file key_file: valid_key_file bearer_token: mysecret - job_name: service-kubernetes kubernetes_sd_configs: - role: endpoints api_server: 'https://localhost:1234' basic_auth: username: 'myusername' password: 'mysecret' - job_name: service-kubernetes-namespaces kubernetes_sd_configs: - role: endpoints api_server: 'https://localhost:1234' namespaces: names: - default - job_name: service-marathon marathon_sd_configs: - servers: - 'https://marathon.example.com:443' tls_config: cert_file: valid_cert_file key_file: valid_key_file - job_name: service-ec2 ec2_sd_configs: - region: us-east-1 access_key: access secret_key: mysecret profile: profile - job_name: service-azure azure_sd_configs: - subscription_id: 11AAAA11-A11A-111A-A111-1111A1111A11 tenant_id: BBBB222B-B2B2-2B22-B222-2BB2222BB2B2 client_id: 333333CC-3C33-3333-CCC3-33C3CCCCC33C client_secret: mysecret port: 9100 - job_name: service-nerve nerve_sd_configs: - servers: - localhost paths: - /monitoring - job_name: 0123service-xxx metrics_path: /metrics static_configs: - targets: - localhost:9090 - job_name: 測試 metrics_path: /metrics static_configs: - targets: - localhost:9090 - job_name: service-triton triton_sd_configs: - account: 'testAccount' dns_suffix: 'triton.example.com' endpoint: 'triton.example.com' port: 9163 refresh_interval: 1m version: 1 tls_config: cert_file: testdata/valid_cert_file key_file: testdata/valid_key_file alerting: alertmanagers: - scheme: https static_configs: - targets: - "1.2.3.4:9093" - "1.2.3.5:9093" - "1.2.3.6:9093" prometheus-2.1.0+ds/config/testdata/first.rules000066400000000000000000000003251323116307200215660ustar00rootroot00000000000000groups: - name: my-group-name rules: - alert: InstanceDown expr: up == 0 for: 1m labels: severity: critical annotations: description: "stuff's happening with {{ $labels.service }}" prometheus-2.1.0+ds/config/testdata/global_timeout.good.yml000066400000000000000000000000631323116307200240420ustar00rootroot00000000000000global: scrape_timeout: 1h scrape_interval: 1h prometheus-2.1.0+ds/config/testdata/jobname.bad.yml000066400000000000000000000000361323116307200222450ustar00rootroot00000000000000scrape_configs: - job_name: prometheus-2.1.0+ds/config/testdata/jobname_dup.bad.yml000066400000000000000000000002301323116307200231110ustar00rootroot00000000000000# Two scrape configs with the same job names are not allowed. scrape_configs: - job_name: prometheus - job_name: service-x - job_name: prometheus prometheus-2.1.0+ds/config/testdata/kubernetes_bearertoken.bad.yml000066400000000000000000000002761323116307200253700ustar00rootroot00000000000000scrape_configs: - job_name: prometheus kubernetes_sd_configs: - role: node api_server: 'https://localhost:1234' bearer_token: 1234 bearer_token_file: somefile prometheus-2.1.0+ds/config/testdata/kubernetes_bearertoken_basicauth.bad.yml000066400000000000000000000003371323116307200274110ustar00rootroot00000000000000scrape_configs: - job_name: prometheus kubernetes_sd_configs: - role: pod api_server: 'https://localhost:1234' bearer_token: 1234 basic_auth: username: user password: password prometheus-2.1.0+ds/config/testdata/kubernetes_namespace_discovery.bad.yml000066400000000000000000000001731323116307200271060ustar00rootroot00000000000000scrape_configs: - kubernetes_sd_configs: - api_server: kubernetes:443 role: endpoints namespaces: foo: bar prometheus-2.1.0+ds/config/testdata/kubernetes_role.bad.yml000066400000000000000000000001341323116307200240210ustar00rootroot00000000000000scrape_configs: - kubernetes_sd_configs: - api_server: kubernetes:443 role: vacation prometheus-2.1.0+ds/config/testdata/labeldrop.bad.yml000066400000000000000000000001701323116307200225750ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - source_labels: [abcdef] action: labeldrop prometheus-2.1.0+ds/config/testdata/labeldrop2.bad.yml000066400000000000000000000001541323116307200226610ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - modulus: 8 action: labeldrop prometheus-2.1.0+ds/config/testdata/labeldrop3.bad.yml000066400000000000000000000001601323116307200226570ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - separator: ',' action: labeldrop prometheus-2.1.0+ds/config/testdata/labeldrop4.bad.yml000066400000000000000000000001671323116307200226670ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - replacement: yolo-{1} action: labeldrop prometheus-2.1.0+ds/config/testdata/labeldrop5.bad.yml000066400000000000000000000001631323116307200226640ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - target_label: yolo action: labeldrop prometheus-2.1.0+ds/config/testdata/labelkeep.bad.yml000066400000000000000000000001701323116307200225550ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - source_labels: [abcdef] action: labelkeep prometheus-2.1.0+ds/config/testdata/labelkeep2.bad.yml000066400000000000000000000001541323116307200226410ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - modulus: 8 action: labelkeep prometheus-2.1.0+ds/config/testdata/labelkeep3.bad.yml000066400000000000000000000001601323116307200226370ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - separator: ',' action: labelkeep prometheus-2.1.0+ds/config/testdata/labelkeep4.bad.yml000066400000000000000000000001671323116307200226470ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - replacement: yolo-{1} action: labelkeep prometheus-2.1.0+ds/config/testdata/labelkeep5.bad.yml000066400000000000000000000001631323116307200226440ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - target_label: yolo action: labelkeep prometheus-2.1.0+ds/config/testdata/labelname.bad.yml000066400000000000000000000000621323116307200225510ustar00rootroot00000000000000global: external_labels: not$allowed: value prometheus-2.1.0+ds/config/testdata/labelname2.bad.yml000066400000000000000000000000641323116307200226350ustar00rootroot00000000000000global: external_labels: 'not:allowed': value prometheus-2.1.0+ds/config/testdata/marathon_no_servers.bad.yml000066400000000000000000000002441323116307200247110ustar00rootroot00000000000000# my global config global: scrape_interval: 15s evaluation_interval: 30s scrape_configs: - job_name: service-marathon marathon_sd_configs: - servers: prometheus-2.1.0+ds/config/testdata/modulus_missing.bad.yml000066400000000000000000000001541323116307200240540ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - regex: abcdef action: hashmod prometheus-2.1.0+ds/config/testdata/regex.bad.yml000066400000000000000000000001251323116307200217430ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - regex: abc(def prometheus-2.1.0+ds/config/testdata/remote_read_url_missing.bad.yml000066400000000000000000000000261323116307200255320ustar00rootroot00000000000000remote_read: - url: prometheus-2.1.0+ds/config/testdata/remote_write_url_missing.bad.yml000066400000000000000000000000271323116307200257520ustar00rootroot00000000000000remote_write: - url: prometheus-2.1.0+ds/config/testdata/rules.bad.yml000066400000000000000000000000671323116307200217700ustar00rootroot00000000000000rule_files: - 'my_rule' # fine - 'my/*/rule' # bad prometheus-2.1.0+ds/config/testdata/rules_abs_path.good.yml000066400000000000000000000001311323116307200240230ustar00rootroot00000000000000rule_files: - 'first.rules' - 'rules/second.rules' - '/absolute/third.rules' prometheus-2.1.0+ds/config/testdata/rules_abs_path_windows.good.yml000066400000000000000000000001331323116307200255770ustar00rootroot00000000000000rule_files: - 'first.rules' - 'rules\second.rules' - 'c:\absolute\third.rules' prometheus-2.1.0+ds/config/testdata/scrape_interval.bad.yml000066400000000000000000000001231323116307200240100ustar00rootroot00000000000000scrape_configs: - job_name: prometheus scrape_interval: 5s scrape_timeout: 6s prometheus-2.1.0+ds/config/testdata/static_config.bad.json000066400000000000000000000001741323116307200236210ustar00rootroot00000000000000{ "targets": ["1.2.3.4:9100"], "labels": { "some_valid_label": "foo", "oops:this-label-is-invalid": "bar" } } prometheus-2.1.0+ds/config/testdata/target_label_hashmod_missing.bad.yml000066400000000000000000000002331323116307200265120ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - source_labels: [__address__] modulus: 8 action: hashmod prometheus-2.1.0+ds/config/testdata/target_label_missing.bad.yml000066400000000000000000000001241323116307200250060ustar00rootroot00000000000000scrape_configs: - job_name: prometheus relabel_configs: - regex: abcdef prometheus-2.1.0+ds/config/testdata/unknown_attr.bad.yml000066400000000000000000000005531323116307200233670ustar00rootroot00000000000000# my global config global: scrape_interval: 15s evaluation_interval: 30s # scrape_timeout is set to the global default (10s). external_labels: monitor: codelab foo: bar rule_files: - "first.rules" - "second.rules" - "my/*.rules" scrape_configs: - job_name: prometheus consult_sd_configs: - server: 'localhost:1234' prometheus-2.1.0+ds/config/testdata/unknown_global_attr.bad.yml000066400000000000000000000000421323116307200247000ustar00rootroot00000000000000global: nonexistent_field: test prometheus-2.1.0+ds/config/testdata/url_in_targetgroup.bad.yml000066400000000000000000000001271323116307200245460ustar00rootroot00000000000000scrape_configs: - job_name: prometheus static_configs: - targets: - http://bad prometheus-2.1.0+ds/console_libraries/000077500000000000000000000000001323116307200200035ustar00rootroot00000000000000prometheus-2.1.0+ds/console_libraries/menu.lib000066400000000000000000000047711323116307200214500ustar00rootroot00000000000000{{/* vim: set ft=html: */}} {{/* Navbar, should be passed . */}} {{ define "navbar" }} {{ end }} {{/* LHS menu, should be passed . */}} {{ define "menu" }}
    {{ template "_menuItem" (args . "index.html.example" "Overview") }} {{ if query "up{job='node'}" }} {{ template "_menuItem" (args . "node.html" "Node") }} {{ if match "^node" .Path }} {{ if .Params.instance }} {{ end }} {{ end }} {{ end }} {{ if query "up{job='prometheus'}" }} {{ template "_menuItem" (args . "prometheus.html" "Prometheus") }} {{ if match "^prometheus" .Path }} {{ if .Params.instance }} {{ end }} {{ end }} {{ end }}
{{ end }} {{/* Helper, pass (args . path name) */}} {{ define "_menuItem" }}
  • {{ .arg2 }}
  • {{ end }} prometheus-2.1.0+ds/console_libraries/prom.lib000066400000000000000000000130361323116307200214530ustar00rootroot00000000000000{{/* vim: set ft=html: */}} {{/* Load Prometheus console library JS/CSS. Should go in */}} {{ define "prom_console_head" }} {{ end }} {{/* Top of all pages. */}} {{ define "head" }} {{ template "prom_console_head" }} {{ template "navbar" . }} {{ template "menu" . }} {{ end }} {{ define "__prom_query_drilldown_noop" }}{{ . }}{{ end }} {{ define "humanize" }}{{ humanize . }}{{ end }} {{ define "humanizeNoSmallPrefix" }}{{ if and (lt . 1.0) (gt . -1.0) }}{{ printf "%.3g" . }}{{ else }}{{ humanize . }}{{ end }}{{ end }} {{ define "humanize1024" }}{{ humanize1024 . }}{{ end }} {{ define "humanizeDuration" }}{{ humanizeDuration . }}{{ end }} {{ define "humanizeTimestamp" }}{{ humanizeTimestamp . }}{{ end }} {{ define "printf.1f" }}{{ printf "%.1f" . }}{{ end }} {{ define "printf.3g" }}{{ printf "%.3g" . }}{{ end }} {{/* prom_query_drilldown (args expr suffix? renderTemplate?) Displays the result of the expression, with a link to /graph for it. renderTemplate is the name of the template to use to render the value. */}} {{ define "prom_query_drilldown" }} {{ $expr := .arg0 }}{{ $suffix := (or .arg1 "") }}{{ $renderTemplate := (or .arg2 "__prom_query_drilldown_noop") }} {{ with query $expr }}{{tmpl $renderTemplate ( . | first | value )}}{{ $suffix }}{{ else }}-{{ end }} {{ end }} {{ define "prom_path" }}/consoles/{{ .Path }}?{{ range $param, $value := .Params }}{{ $param }}={{ $value }}&{{ end }}{{ end }}" {{ define "prom_right_table_head" }}
    {{ end }} {{ define "prom_right_table_tail" }}
    {{ end }} {{/* RHS table head, pass job name. Should be used after prom_right_table_head. */}} {{ define "prom_right_table_job_head" }} {{ . }} {{ template "prom_query_drilldown" (args (printf "sum(up{job='%s'})" .)) }} / {{ template "prom_query_drilldown" (args (printf "count(up{job='%s'})" .)) }} CPU {{ template "prom_query_drilldown" (args (printf "avg by(job)(irate(process_cpu_seconds_total{job='%s'}[5m]))" .) "s/s" "humanizeNoSmallPrefix") }} Memory {{ template "prom_query_drilldown" (args (printf "avg by(job)(process_resident_memory_bytes{job='%s'})" .) "B" "humanize1024") }} {{ end }} {{ define "prom_content_head" }}
    {{ template "prom_graph_timecontrol" . }} {{ end }} {{ define "prom_content_tail" }}
    {{ end }} {{ define "prom_graph_timecontrol" }}
    {{ end }} {{/* Bottom of all pages. */}} {{ define "tail" }} {{ end }} prometheus-2.1.0+ds/consoles/000077500000000000000000000000001323116307200161325ustar00rootroot00000000000000prometheus-2.1.0+ds/consoles/index.html.example000066400000000000000000000011571323116307200215650ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Overview

    These are example consoles for Prometheus.

    These consoles expect exporters to have the following job labels:

    Exporter Job label
    Node Exporter node
    Prometheus prometheus
    {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/node-cpu.html000066400000000000000000000050331323116307200205330ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} CPU(s): {{ template "prom_query_drilldown" (args (printf "scalar(count(count by (cpu)(node_cpu{job='node',instance='%s'})))" .Params.instance)) }} {{ range printf "sum by (mode)(irate(node_cpu{job='node',instance='%s'}[5m])) * 100 / scalar(count(count by (cpu)(node_cpu{job='node',instance='%s'})))" .Params.instance .Params.instance | query | sortByLabel "mode" }} {{ .Labels.mode | title }} CPU {{ .Value | printf "%.1f" }}% {{ end }} Misc Processes Running {{ template "prom_query_drilldown" (args (printf "node_procs_running{job='node',instance='%s'}" .Params.instance) "" "humanize") }} Processes Blocked {{ template "prom_query_drilldown" (args (printf "node_procs_blocked{job='node',instance='%s'}" .Params.instance) "" "humanize") }} Forks {{ template "prom_query_drilldown" (args (printf "irate(node_forks{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }} Context Switches {{ template "prom_query_drilldown" (args (printf "irate(node_context_switches{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }} Interrupts {{ template "prom_query_drilldown" (args (printf "irate(node_intr{job='node',instance='%s'}[5m])" .Params.instance) "/s" "humanize") }} 1m Loadavg {{ template "prom_query_drilldown" (args (printf "node_load1{job='node',instance='%s'}" .Params.instance)) }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Node CPU - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}

    CPU Usage

    {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/node-disk.html000066400000000000000000000065651323116307200207110ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} Disks {{ range printf "node_disk_io_time_ms{job='node',instance='%s'}" .Params.instance | query | sortByLabel "device" }} {{ .Labels.device }} Utilization {{ template "prom_query_drilldown" (args (printf "irate(node_disk_io_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 * 100" .Labels.instance .Labels.device) "%" "printf.1f") }} Throughput {{ template "prom_query_drilldown" (args (printf "irate(node_disk_sectors_read{job='node',instance='%s',device='%s'}[5m]) * 512 + irate(node_disk_sectors_written{job='node',instance='%s',device='%s'}[5m]) * 512" .Labels.instance .Labels.device .Labels.instance .Labels.device) "B/s" "humanize") }} Avg Read Time {{ template "prom_query_drilldown" (args (printf "irate(node_disk_read_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 / irate(node_disk_reads_completed{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device .Labels.instance .Labels.device) "s" "humanize") }} Avg Write Time {{ template "prom_query_drilldown" (args (printf "irate(node_disk_write_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 / irate(node_disk_writes_completed{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device .Labels.instance .Labels.device) "s" "humanize") }} {{ end }} Filesystem Fullness {{ define "roughlyNearZero" }} {{ if gt .1 . }}~0{{ else }}{{ printf "%.1f" . }}{{ end }} {{ end }} {{ range printf "node_filesystem_size{job='node',instance='%s'}" .Params.instance | query | sortByLabel "mountpoint" }} {{ .Labels.mountpoint }} {{ template "prom_query_drilldown" (args (printf "100 - node_filesystem_free{job='node',instance='%s',mountpoint='%s'} / node_filesystem_size{job='node'} * 100" .Labels.instance .Labels.mountpoint) "%" "roughlyNearZero") }} {{ end }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Node Disk - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}

    Disk I/O Utilization

    Filesystem Usage

    {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/node-overview.html000066400000000000000000000127361323116307200216220ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} Overview User CPU {{ template "prom_query_drilldown" (args (printf "sum(irate(node_cpu{job='node',instance='%s',mode='user'}[5m])) * 100 / count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance .Params.instance) "%" "printf.1f") }} System CPU {{ template "prom_query_drilldown" (args (printf "sum(irate(node_cpu{job='node',instance='%s',mode='system'}[5m])) * 100 / count(count by (cpu)(node_cpu{job='node',instance='%s'}))" .Params.instance .Params.instance) "%" "printf.1f") }} Memory Total {{ template "prom_query_drilldown" (args (printf "node_memory_MemTotal{job='node',instance='%s'}" .Params.instance) "B" "humanize1024") }} Memory Free {{ template "prom_query_drilldown" (args (printf "node_memory_MemFree{job='node',instance='%s'}" .Params.instance) "B" "humanize1024") }} Network {{ range printf "node_network_receive_bytes{job='node',instance='%s',device!='lo'}" .Params.instance | query | sortByLabel "device" }} {{ .Labels.device }} Received {{ template "prom_query_drilldown" (args (printf "irate(node_network_receive_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device) "B/s" "humanize") }} {{ .Labels.device }} Transmitted {{ template "prom_query_drilldown" (args (printf "irate(node_network_transmit_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device) "B/s" "humanize") }} {{ end }} Disks {{ range printf "node_disk_io_time_ms{job='node',instance='%s',device!~'^(md\\\\d+$|dm-)'}" .Params.instance | query | sortByLabel "device" }} {{ .Labels.device }} Utilization {{ template "prom_query_drilldown" (args (printf "irate(node_disk_io_time_ms{job='node',instance='%s',device='%s'}[5m]) / 1000 * 100" .Labels.instance .Labels.device) "%" "printf.1f") }} {{ end }} {{ range printf "node_disk_io_time_ms{job='node',instance='%s'}" .Params.instance | query | sortByLabel "device" }} {{ .Labels.device }} Throughput {{ template "prom_query_drilldown" (args (printf "irate(node_disk_sectors_read{job='node',instance='%s',device='%s'}[5m]) * 512 + irate(node_disk_sectors_written{job='node',instance='%s',device='%s'}[5m]) * 512" .Labels.instance .Labels.device .Labels.instance .Labels.device) "B/s" "humanize") }} {{ end }} Filesystem Fullness {{ define "roughlyNearZero" }} {{ if gt .1 . }}~0{{ else }}{{ printf "%.1f" . }}{{ end }} {{ end }} {{ range printf "node_filesystem_size{job='node',instance='%s'}" .Params.instance | query | sortByLabel "mountpoint" }} {{ .Labels.mountpoint }} {{ template "prom_query_drilldown" (args (printf "100 - node_filesystem_free{job='node',instance='%s',mountpoint='%s'} / node_filesystem_size{job='node'} * 100" .Labels.instance .Labels.mountpoint) "%" "roughlyNearZero") }} {{ end }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Node Overview - {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Params.instance }}

    CPU Usage

    Disk I/O Utilization

    Memory

    {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/node.html000066400000000000000000000026041323116307200177470ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} Node {{ template "prom_query_drilldown" (args "sum(up{job='node'})") }} / {{ template "prom_query_drilldown" (args "count(up{job='node'})") }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Node

    {{ range query "up{job='node'}" | sortByLabel "instance" }} Yes{{ else }} class="alert-danger">No{{ end }} {{ else }} {{ end }} {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/prometheus-overview.html000066400000000000000000000100401323116307200230520ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ range printf "http_request_duration_microseconds_count{job='prometheus',instance='%s',handler=~'^(query.*|federate|consoles)$'}" .Params.instance | query | sortByLabel "handler" }} {{ end }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Prometheus Overview - {{ .Params.instance }}

    Ingested Samples

    HTTP Server

    {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/consoles/prometheus.html000066400000000000000000000024641323116307200212210ustar00rootroot00000000000000{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail" }} {{ template "prom_content_head" . }}

    Prometheus

    Node Up CPU
    Used
    Memory
    Available
    {{ reReplaceAll "(.*?://)([^:/]+?)(:\\d+)?/.*" "$2" .Labels.instance }} {{ template "prom_query_drilldown" (args (printf "100 * (1 - avg by(instance)(irate(node_cpu{job='node',mode='idle',instance='%s'}[5m])))" .Labels.instance) "%" "printf.1f") }} {{ template "prom_query_drilldown" (args (printf "node_memory_MemFree{job='node',instance='%s'} + node_memory_Cached{job='node',instance='%s'} + node_memory_Buffers{job='node',instance='%s'}" .Labels.instance .Labels.instance .Labels.instance) "B" "humanize1024") }}
    No nodes found.
    Overview
    CPU {{ template "prom_query_drilldown" (args (printf "irate(process_cpu_seconds_total{job='prometheus',instance='%s'}[5m])" .Params.instance) "s/s" "humanizeNoSmallPrefix") }}
    Memory {{ template "prom_query_drilldown" (args (printf "process_resident_memory_bytes{job='prometheus',instance='%s'}" .Params.instance) "B" "humanize1024") }}
    Version {{ with query (printf "prometheus_build_info{job='prometheus',instance='%s'}" .Params.instance) }}{{. | first | label "version"}}{{end}}
    Storage
    Ingested Samples {{ template "prom_query_drilldown" (args (printf "irate(prometheus_tsdb_head_samples_appended_total{job='prometheus',instance='%s'}[5m])" .Params.instance) "/s" "humanizeNoSmallPrefix") }}
    Head Series {{ template "prom_query_drilldown" (args (printf "prometheus_tsdb_head_series{job='prometheus',instance='%s'}" .Params.instance) "" "humanize") }}
    Blocks Loaded {{ template "prom_query_drilldown" (args (printf "prometheus_tsdb_blocks_loaded{job='prometheus',instance='%s'}" .Params.instance) "" "humanize") }}
    Rules
    Evaluation Duration {{ template "prom_query_drilldown" (args (printf "irate(prometheus_evaluator_duration_seconds_sum{job='prometheus',instance='%s'}[5m]) / irate(prometheus_evaluator_duration_seconds_count{job='prometheus',instance='%s'}[5m])" .Params.instance .Params.instance) "" "humanizeDuration") }}
    Notification Latency {{ template "prom_query_drilldown" (args (printf "irate(prometheus_notifications_latency_seconds_sum{job='prometheus',instance='%s'}[5m]) / irate(prometheus_notifications_latency_seconds_count{job='prometheus',instance='%s'}[5m])" .Params.instance .Params.instance) "" "humanizeDuration") }}
    Notification Queue {{ template "prom_query_drilldown" (args (printf "prometheus_notifications_queue_length{job='prometheus',instance='%s'}" .Params.instance) "" "humanize") }}
    HTTP Server
    {{ .Labels.handler }} {{ template "prom_query_drilldown" (args (printf "irate(http_request_duration_microseconds_count{job='prometheus',instance='%s',handler='%s'}[5m])" .Labels.instance .Labels.handler) "/s" "humanizeNoSmallPrefix") }}
    Prometheus {{ template "prom_query_drilldown" (args "sum(up{job='prometheus'})") }} / {{ template "prom_query_drilldown" (args "count(up{job='prometheus'})") }}
    {{ range query "up{job='prometheus'}" | sortByLabel "instance" }} {{ else }} {{ end }} {{ template "prom_content_tail" . }} {{ template "tail" }} prometheus-2.1.0+ds/discovery/000077500000000000000000000000001323116307200163145ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/README.md000066400000000000000000000227041323116307200176000ustar00rootroot00000000000000### Service Discovery This directory contains the service discovery (SD) component of Prometheus. There is currently a moratorium on new service discovery mechanisms being added to Prometheus due to a lack of developer capacity. In the meantime `file_sd` remains available. ## Design of a Prometheus SD There are many requests to add new SDs to Prometheus, this section looks at what makes a good SD and covers some of the common implementation issues. ### Does this make sense as an SD? The first question to be asked is does it make sense to add this particular SD? An SD mechanism should be reasonably well established, and at a minimum in use across multiple organisations. It should allow discovering of machines and/or services running somewhere. When exactly an SD is popular enough to justify being added to Prometheus natively is an open question. It should not be a brand new SD mechanism, or a variant of an established mechanism. We want to integrate Prometheus with the SD that's already there in your infrastructure, not invent yet more ways to do service discovery. We also do not add mechanisms to work around users lacking service discovery and/or configuration management infrastructure. SDs that merely discover other applications running the same software (e.g. talk to one Kafka or Cassandra server to find the others) are not service discovery. In that case the SD you should be looking at is whatever decides that a machine is going to be a Kafka server, likely a machine database or configuration management system. If something is particularly custom or unusual, `file_sd` is the generic mechanism provided for users to hook in. Generally with Prometheus we offer a single generic mechanism for things with infinite variations, rather than trying to support everything natively (see also, alertmanager webhook, remote read, remote write, node exporter textfile collector). For example anything that would involve talking to a relational database should use `file_sd` instead. For configuration management systems like Chef, while they do have a database/API that'd in principle make sense to talk to for service discovery, the idiomatic approach is to use Chef's templating facilities to write out a file for use with `file_sd`. ### Mapping from SD to Prometheus The general principle with SD is to extract all the potentially useful information we can out of the SD, and let the user choose what they need of it using [relabelling](https://prometheus.io/docs/operating/configuration/#). This information is generally termed metadata. Metadata is exposed as a set of key/value pairs (labels) per target. The keys are prefixed with `__meta__`, and there should also be an `__address__` label with the host:port of the target (preferably an IP address to avoid DNS lookups). No other labelnames should be exposed. It is very common for initial pull requests for new SDs to include hardcoded assumptions that make sense for the the author's setup. SD should be generic, any customisation should be handled via relabelling. There should be basically no business logic, filtering, or transformations of the data from the SD beyond that which is needed to fit it into the metadata data model. Arrays (e.g. a list of tags) should be converted to a single label with the array values joined with a comma. Also prefix and suffix the value with a comma. So for example the array `[a, b, c]` would become `,a,b,c,`. As relabelling regexes are fully anchored, this makes it easier to write correct regexes against (`.*,a,.*` works no matter where `a` appears in the list). The canonical example of this is `__meta_consul_tags`. Maps, hashes and other forms of key/value pairs should be all prefixed and exposed as labels. For example for EC2 tags, there would be `__meta_ec2_tag_Description=mydescription` for the Description tag. Labelnames may only contain `[_a-zA-Z0-9]`, sanitize by replacing with underscores as needed. For targets with multiple potential ports, you can a) expose them as a list, b) if they're named expose them as a map or c) expose them each as their own target. Kubernetes SD takes the target per port approach. a) and b) can be combined. For machine-like SDs (OpenStack, EC2, Kubernetes to some extent) there may be multiple network interfaces for a target. Thus far reporting the details of only the first/primary network interface has sufficed. ### Other implementation considerations SDs are intended to dump all possible targets. For example the optional use of EC2 service discovery would be to take the entire region's worth of EC2 instances it provides and do everything needed in one `scrape_config`. For large deployments where you are only interested in a small proportion of the returned targets, this may cause performance issues. If this occurs it is acceptable to also offer filtering via whatever mechanisms the SD exposes. For EC2 that would be the `Filter` option on `DescribeInstances`. Keep in mind that this is a performance optimisation, it should be possible to do the same filtering using relabelling alone. As with SD generally, we do not invent new ways to filter targets (that is what relabelling is for), merely offer up whatever functionality the SD itself offers. It is a general rule with Prometheus that all configuration comes from the configuration file. While the libraries you use to talk to the SD may also offer other mechanisms for providing configuration/authentication under the covers (EC2's use of environment variables being a prime example), using your SD mechanism should not require this. Put another way, your SD implementation should not read environment variables or files to obtain configuration. Some SD mechanisms have rate limits that make them challenging to use. As an example we have unfortunately had to reject Amazon ECS service discovery due to the rate limits being so low that it would not be usable for anything beyond small setups. If a system offers multiple distinct types of SD, select which is in use with a configuration option rather than returning them all from one mega SD that requires relabelling to select just the one you want. So far we have only seen this with Kubernetes. When a single SD with a selector vs. multiple distinct SDs makes sense is an open question. If there is a failure while processing talking to the SD, abort rather than returning partial data. It is better to work from stale targets than partial or incorrect metadata. The information obtained from service discovery is not considered sensitive security wise. Do not return secrets in metadata, anyone with access to the Prometheus server will be able to see them. ## Writing an SD mechanism ### The SD interface A Service Discovery (SD) mechanism has to discover targets and provide them to Prometheus. We expect similar targets to be grouped together, in the form of a [`TargetGroup`](https://godoc.org/github.com/prometheus/prometheus/config#TargetGroup). The SD mechanism sends the targets down to prometheus as list of `TargetGroups`. An SD mechanism has to implement the `Discoverer` Interface: ```go type Discoverer interface { Run(ctx context.Context, up chan<- []*config.TargetGroup) } ``` Prometheus will call the `Run()` method on a provider to initialise the discovery mechanism. The mechanism will then send *all* the `TargetGroup`s into the channel. Now the mechanism will watch for changes and then send only changed and new `TargetGroup`s down the channel. For example if we had a discovery mechanism and it retrieves the following groups: ``` []config.TargetGroup{ { Targets: []model.LabelSet{ { "__instance__": "10.11.150.1:7870", "hostname": "demo-target-1", "test": "simple-test", }, { "__instance__": "10.11.150.4:7870", "hostname": "demo-target-2", "test": "simple-test", }, }, Labels: map[LabelName][LabelValue] { "job": "mysql", }, "Source": "file1", }, { Targets: []model.LabelSet{ { "__instance__": "10.11.122.11:6001", "hostname": "demo-postgres-1", "test": "simple-test", }, { "__instance__": "10.11.122.15:6001", "hostname": "demo-postgres-2", "test": "simple-test", }, }, Labels: map[LabelName][LabelValue] { "job": "postgres", }, "Source": "file2", }, } ``` Here there are two `TargetGroups` one group with source `file1` and another with `file2`. The grouping is implementation specific and could even be one target per group. But, one has to make sure every target group sent by an SD instance should have a `Source` which is unique across all the `TargetGroup`s of that SD instance. In this case, both the `TargetGroup`s are sent down the channel the first time `Run()` is called. Now, for an update, we need to send the whole _changed_ `TargetGroup` down the channel. i.e, if the target with `hostname: demo-postgres-2` goes away, we send: ``` &config.TargetGroup{ Targets: []model.LabelSet{ { "__instance__": "10.11.122.11:6001", "hostname": "demo-postgres-1", "test": "simple-test", }, }, Labels: map[LabelName][LabelValue] { "job": "postgres", }, "Source": "file2", } ``` down the channel. If all the targets in a group go away, we need to send the target groups with empty `Targets` down the channel. i.e, if all targets with `job: postgres` go away, we send: ``` &config.TargetGroup{ Targets: nil, "Source": "file2", } ``` down the channel. prometheus-2.1.0+ds/discovery/azure/000077500000000000000000000000001323116307200174425ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/azure/azure.go000066400000000000000000000235441323116307200211270ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package azure import ( "context" "fmt" "net" "strings" "time" "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/go-autorest/autorest/azure" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( azureLabel = model.MetaLabelPrefix + "azure_" azureLabelMachineID = azureLabel + "machine_id" azureLabelMachineResourceGroup = azureLabel + "machine_resource_group" azureLabelMachineName = azureLabel + "machine_name" azureLabelMachineLocation = azureLabel + "machine_location" azureLabelMachinePrivateIP = azureLabel + "machine_private_ip" azureLabelMachineTag = azureLabel + "machine_tag_" ) var ( azureSDRefreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_azure_refresh_failures_total", Help: "Number of Azure-SD refresh failures.", }) azureSDRefreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_azure_refresh_duration_seconds", Help: "The duration of a Azure-SD refresh in seconds.", }) // DefaultSDConfig is the default Azure SD configuration. DefaultSDConfig = SDConfig{ Port: 80, RefreshInterval: model.Duration(5 * time.Minute), } ) // SDConfig is the configuration for Azure based service discovery. type SDConfig struct { Port int `yaml:"port"` SubscriptionID string `yaml:"subscription_id"` TenantID string `yaml:"tenant_id,omitempty"` ClientID string `yaml:"client_id,omitempty"` ClientSecret config_util.Secret `yaml:"client_secret,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "azure_sd_config") } func init() { prometheus.MustRegister(azureSDRefreshDuration) prometheus.MustRegister(azureSDRefreshFailuresCount) } // Discovery periodically performs Azure-SD requests. It implements // the Discoverer interface. type Discovery struct { cfg *SDConfig interval time.Duration port int logger log.Logger } // NewDiscovery returns a new AzureDiscovery which periodically refreshes its targets. func NewDiscovery(cfg *SDConfig, logger log.Logger) *Discovery { if logger == nil { logger = log.NewNopLogger() } return &Discovery{ cfg: cfg, interval: time.Duration(cfg.RefreshInterval), port: cfg.Port, logger: logger, } } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { ticker := time.NewTicker(d.interval) defer ticker.Stop() for { select { case <-ctx.Done(): return default: } tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Unable to refresh during Azure discovery", "err", err) } else { select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } select { case <-ticker.C: case <-ctx.Done(): return } } } // azureClient represents multiple Azure Resource Manager providers. type azureClient struct { nic network.InterfacesClient vm compute.VirtualMachinesClient } // createAzureClient is a helper function for creating an Azure compute client to ARM. func createAzureClient(cfg SDConfig) (azureClient, error) { var c azureClient oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(cfg.TenantID) if err != nil { return azureClient{}, err } spt, err := azure.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, string(cfg.ClientSecret), azure.PublicCloud.ResourceManagerEndpoint) if err != nil { return azureClient{}, err } c.vm = compute.NewVirtualMachinesClient(cfg.SubscriptionID) c.vm.Authorizer = spt c.nic = network.NewInterfacesClient(cfg.SubscriptionID) c.nic.Authorizer = spt return c, nil } // azureResource represents a resource identifier in Azure. type azureResource struct { Name string ResourceGroup string } // Create a new azureResource object from an ID string. func newAzureResourceFromID(id string, logger log.Logger) (azureResource, error) { // Resource IDs have the following format. // /subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP/providers/PROVIDER/TYPE/NAME s := strings.Split(id, "/") if len(s) != 9 { err := fmt.Errorf("invalid ID '%s'. Refusing to create azureResource", id) level.Error(logger).Log("err", err) return azureResource{}, err } return azureResource{ Name: strings.ToLower(s[8]), ResourceGroup: strings.ToLower(s[4]), }, nil } func (d *Discovery) refresh() (tg *targetgroup.Group, err error) { defer level.Debug(d.logger).Log("msg", "Azure discovery completed") t0 := time.Now() defer func() { azureSDRefreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { azureSDRefreshFailuresCount.Inc() } }() tg = &targetgroup.Group{} client, err := createAzureClient(*d.cfg) if err != nil { return tg, fmt.Errorf("could not create Azure client: %s", err) } var machines []compute.VirtualMachine result, err := client.vm.ListAll() if err != nil { return tg, fmt.Errorf("could not list virtual machines: %s", err) } machines = append(machines, *result.Value...) // If we still have results, keep going until we have no more. for result.NextLink != nil { result, err = client.vm.ListAllNextResults(result) if err != nil { return tg, fmt.Errorf("could not list virtual machines: %s", err) } machines = append(machines, *result.Value...) } level.Debug(d.logger).Log("msg", "Found virtual machines during Azure discovery.", "count", len(machines)) // We have the slice of machines. Now turn them into targets. // Doing them in go routines because the network interface calls are slow. type target struct { labelSet model.LabelSet err error } ch := make(chan target, len(machines)) for i, vm := range machines { go func(i int, vm compute.VirtualMachine) { r, err := newAzureResourceFromID(*vm.ID, d.logger) if err != nil { ch <- target{labelSet: nil, err: err} return } labels := model.LabelSet{ azureLabelMachineID: model.LabelValue(*vm.ID), azureLabelMachineName: model.LabelValue(*vm.Name), azureLabelMachineLocation: model.LabelValue(*vm.Location), azureLabelMachineResourceGroup: model.LabelValue(r.ResourceGroup), } if vm.Tags != nil { for k, v := range *vm.Tags { name := strutil.SanitizeLabelName(k) labels[azureLabelMachineTag+model.LabelName(name)] = model.LabelValue(*v) } } // Get the IP address information via separate call to the network provider. for _, nic := range *vm.Properties.NetworkProfile.NetworkInterfaces { r, err := newAzureResourceFromID(*nic.ID, d.logger) if err != nil { ch <- target{labelSet: nil, err: err} return } networkInterface, err := client.nic.Get(r.ResourceGroup, r.Name, "") if err != nil { level.Error(d.logger).Log("msg", "Unable to get network interface", "name", r.Name, "err", err) ch <- target{labelSet: nil, err: err} // Get out of this routine because we cannot continue without a network interface. return } // Unfortunately Azure does not return information on whether a VM is deallocated. // This information is available via another API call however the Go SDK does not // yet support this. On deallocated machines, this value happens to be nil so it // is a cheap and easy way to determine if a machine is allocated or not. if networkInterface.Properties.Primary == nil { level.Debug(d.logger).Log("msg", "Skipping deallocated virtual machine", "machine", *vm.Name) ch <- target{} return } if *networkInterface.Properties.Primary { for _, ip := range *networkInterface.Properties.IPConfigurations { if ip.Properties.PrivateIPAddress != nil { labels[azureLabelMachinePrivateIP] = model.LabelValue(*ip.Properties.PrivateIPAddress) address := net.JoinHostPort(*ip.Properties.PrivateIPAddress, fmt.Sprintf("%d", d.port)) labels[model.AddressLabel] = model.LabelValue(address) ch <- target{labelSet: labels, err: nil} return } // If we made it here, we don't have a private IP which should be impossible. // Return an empty target and error to ensure an all or nothing situation. err = fmt.Errorf("unable to find a private IP for VM %s", *vm.Name) ch <- target{labelSet: nil, err: err} return } } } }(i, vm) } for range machines { tgt := <-ch if tgt.err != nil { return nil, fmt.Errorf("unable to complete Azure service discovery: %s", err) } if tgt.labelSet != nil { tg.Targets = append(tg.Targets, tgt.labelSet) } } return tg, nil } prometheus-2.1.0+ds/discovery/config/000077500000000000000000000000001323116307200175615ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/config/config.go000066400000000000000000000067521323116307200213670ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "github.com/prometheus/prometheus/discovery/azure" "github.com/prometheus/prometheus/discovery/consul" "github.com/prometheus/prometheus/discovery/dns" "github.com/prometheus/prometheus/discovery/ec2" "github.com/prometheus/prometheus/discovery/file" "github.com/prometheus/prometheus/discovery/gce" "github.com/prometheus/prometheus/discovery/kubernetes" "github.com/prometheus/prometheus/discovery/marathon" "github.com/prometheus/prometheus/discovery/openstack" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/discovery/triton" "github.com/prometheus/prometheus/discovery/zookeeper" yaml_util "github.com/prometheus/prometheus/util/yaml" ) // ServiceDiscoveryConfig configures lists of different service discovery mechanisms. type ServiceDiscoveryConfig struct { // List of labeled target groups for this job. StaticConfigs []*targetgroup.Group `yaml:"static_configs,omitempty"` // List of DNS service discovery configurations. DNSSDConfigs []*dns.SDConfig `yaml:"dns_sd_configs,omitempty"` // List of file service discovery configurations. FileSDConfigs []*file.SDConfig `yaml:"file_sd_configs,omitempty"` // List of Consul service discovery configurations. ConsulSDConfigs []*consul.SDConfig `yaml:"consul_sd_configs,omitempty"` // List of Serverset service discovery configurations. ServersetSDConfigs []*zookeeper.ServersetSDConfig `yaml:"serverset_sd_configs,omitempty"` // NerveSDConfigs is a list of Nerve service discovery configurations. NerveSDConfigs []*zookeeper.NerveSDConfig `yaml:"nerve_sd_configs,omitempty"` // MarathonSDConfigs is a list of Marathon service discovery configurations. MarathonSDConfigs []*marathon.SDConfig `yaml:"marathon_sd_configs,omitempty"` // List of Kubernetes service discovery configurations. KubernetesSDConfigs []*kubernetes.SDConfig `yaml:"kubernetes_sd_configs,omitempty"` // List of GCE service discovery configurations. GCESDConfigs []*gce.SDConfig `yaml:"gce_sd_configs,omitempty"` // List of EC2 service discovery configurations. EC2SDConfigs []*ec2.SDConfig `yaml:"ec2_sd_configs,omitempty"` // List of OpenStack service discovery configurations. OpenstackSDConfigs []*openstack.SDConfig `yaml:"openstack_sd_configs,omitempty"` // List of Azure service discovery configurations. AzureSDConfigs []*azure.SDConfig `yaml:"azure_sd_configs,omitempty"` // List of Triton service discovery configurations. TritonSDConfigs []*triton.SDConfig `yaml:"triton_sd_configs,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *ServiceDiscoveryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain ServiceDiscoveryConfig if err := unmarshal((*plain)(c)); err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "service discovery config") } prometheus-2.1.0+ds/discovery/consul/000077500000000000000000000000001323116307200176175ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/consul/consul.go000066400000000000000000000267511323116307200214640ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package consul import ( "context" "fmt" "net" "net/http" "strconv" "strings" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" consul "github.com/hashicorp/consul/api" "github.com/mwitkow/go-conntrack" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/httputil" "github.com/prometheus/prometheus/util/strutil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( watchTimeout = 30 * time.Second retryInterval = 15 * time.Second // addressLabel is the name for the label containing a target's address. addressLabel = model.MetaLabelPrefix + "consul_address" // nodeLabel is the name for the label containing a target's node name. nodeLabel = model.MetaLabelPrefix + "consul_node" // metaDataLabel is the prefix for the labels mapping to a target's metadata. metaDataLabel = model.MetaLabelPrefix + "consul_metadata_" // tagsLabel is the name of the label containing the tags assigned to the target. tagsLabel = model.MetaLabelPrefix + "consul_tags" // serviceLabel is the name of the label containing the service name. serviceLabel = model.MetaLabelPrefix + "consul_service" // serviceAddressLabel is the name of the label containing the (optional) service address. serviceAddressLabel = model.MetaLabelPrefix + "consul_service_address" //servicePortLabel is the name of the label containing the service port. servicePortLabel = model.MetaLabelPrefix + "consul_service_port" // datacenterLabel is the name of the label containing the datacenter ID. datacenterLabel = model.MetaLabelPrefix + "consul_dc" // serviceIDLabel is the name of the label containing the service ID. serviceIDLabel = model.MetaLabelPrefix + "consul_service_id" // Constants for instrumentation. namespace = "prometheus" ) var ( rpcFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: namespace, Name: "sd_consul_rpc_failures_total", Help: "The number of Consul RPC call failures.", }) rpcDuration = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Namespace: namespace, Name: "sd_consul_rpc_duration_seconds", Help: "The duration of a Consul RPC call in seconds.", }, []string{"endpoint", "call"}, ) // DefaultSDConfig is the default Consul SD configuration. DefaultSDConfig = SDConfig{ TagSeparator: ",", Scheme: "http", } ) // SDConfig is the configuration for Consul service discovery. type SDConfig struct { Server string `yaml:"server"` Token config_util.Secret `yaml:"token,omitempty"` Datacenter string `yaml:"datacenter,omitempty"` TagSeparator string `yaml:"tag_separator,omitempty"` Scheme string `yaml:"scheme,omitempty"` Username string `yaml:"username,omitempty"` Password config_util.Secret `yaml:"password,omitempty"` // The list of services for which targets are discovered. // Defaults to all services if empty. Services []string `yaml:"services"` TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "consul_sd_config"); err != nil { return err } if strings.TrimSpace(c.Server) == "" { return fmt.Errorf("Consul SD configuration requires a server address") } return nil } func init() { prometheus.MustRegister(rpcFailuresCount) prometheus.MustRegister(rpcDuration) // Initialize metric vectors. rpcDuration.WithLabelValues("catalog", "service") rpcDuration.WithLabelValues("catalog", "services") } // Discovery retrieves target information from a Consul server // and updates them via watches. type Discovery struct { client *consul.Client clientConf *consul.Config clientDatacenter string tagSeparator string watchedServices []string // Set of services which will be discovered. logger log.Logger } // NewDiscovery returns a new Discovery for the given config. func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) { if logger == nil { logger = log.NewNopLogger() } tls, err := httputil.NewTLSConfig(conf.TLSConfig) if err != nil { return nil, err } transport := &http.Transport{ TLSClientConfig: tls, DialContext: conntrack.NewDialContextFunc( conntrack.DialWithTracing(), conntrack.DialWithName("consul_sd"), ), } wrapper := &http.Client{ Transport: transport, Timeout: 35 * time.Second, } clientConf := &consul.Config{ Address: conf.Server, Scheme: conf.Scheme, Datacenter: conf.Datacenter, Token: string(conf.Token), HttpAuth: &consul.HttpBasicAuth{ Username: conf.Username, Password: string(conf.Password), }, HttpClient: wrapper, } client, err := consul.NewClient(clientConf) if err != nil { return nil, err } cd := &Discovery{ client: client, clientConf: clientConf, tagSeparator: conf.TagSeparator, watchedServices: conf.Services, clientDatacenter: clientConf.Datacenter, logger: logger, } return cd, nil } // shouldWatch returns whether the service of the given name should be watched. func (d *Discovery) shouldWatch(name string) bool { // If there's no fixed set of watched services, we watch everything. if len(d.watchedServices) == 0 { return true } for _, sn := range d.watchedServices { if sn == name { return true } } return false } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Watched services and their cancelation functions. services := map[string]func(){} var lastIndex uint64 for { catalog := d.client.Catalog() t0 := time.Now() srvs, meta, err := catalog.Services(&consul.QueryOptions{ WaitIndex: lastIndex, WaitTime: watchTimeout, }) rpcDuration.WithLabelValues("catalog", "services").Observe(time.Since(t0).Seconds()) // We have to check the context at least once. The checks during channel sends // do not guarantee that. select { case <-ctx.Done(): return default: } if err != nil { level.Error(d.logger).Log("msg", "Error refreshing service list", "err", err) rpcFailuresCount.Inc() time.Sleep(retryInterval) continue } // If the index equals the previous one, the watch timed out with no update. if meta.LastIndex == lastIndex { continue } lastIndex = meta.LastIndex // If the datacenter was not set from clientConf, let's get it from the local Consul agent // (Consul default is to use local node's datacenter if one isn't given for a query). if d.clientDatacenter == "" { info, err := d.client.Agent().Self() if err != nil { level.Error(d.logger).Log("msg", "Error retrieving datacenter name", "err", err) time.Sleep(retryInterval) continue } d.clientDatacenter = info["Config"]["Datacenter"].(string) } // Check for new services. for name := range srvs { if !d.shouldWatch(name) { continue } if _, ok := services[name]; ok { continue // We are already watching the service. } srv := &consulService{ client: d.client, name: name, labels: model.LabelSet{ serviceLabel: model.LabelValue(name), datacenterLabel: model.LabelValue(d.clientDatacenter), }, tagSeparator: d.tagSeparator, logger: d.logger, } wctx, cancel := context.WithCancel(ctx) go srv.watch(wctx, ch) services[name] = cancel } // Check for removed services. for name, cancel := range services { if _, ok := srvs[name]; !ok { // Call the watch cancelation function. cancel() delete(services, name) // Send clearing target group. select { case <-ctx.Done(): return case ch <- []*targetgroup.Group{{Source: name}}: } } } } } // consulService contains data belonging to the same service. type consulService struct { name string labels model.LabelSet client *consul.Client tagSeparator string logger log.Logger } func (srv *consulService) watch(ctx context.Context, ch chan<- []*targetgroup.Group) { catalog := srv.client.Catalog() lastIndex := uint64(0) for { t0 := time.Now() nodes, meta, err := catalog.Service(srv.name, "", &consul.QueryOptions{ WaitIndex: lastIndex, WaitTime: watchTimeout, }) rpcDuration.WithLabelValues("catalog", "service").Observe(time.Since(t0).Seconds()) // Check the context before potentially falling in a continue-loop. select { case <-ctx.Done(): return default: // Continue. } if err != nil { level.Error(srv.logger).Log("msg", "Error refreshing service", "service", srv.name, "err", err) rpcFailuresCount.Inc() time.Sleep(retryInterval) continue } // If the index equals the previous one, the watch timed out with no update. if meta.LastIndex == lastIndex { continue } lastIndex = meta.LastIndex tgroup := targetgroup.Group{ Source: srv.name, Labels: srv.labels, Targets: make([]model.LabelSet, 0, len(nodes)), } for _, node := range nodes { // We surround the separated list with the separator as well. This way regular expressions // in relabeling rules don't have to consider tag positions. var tags = srv.tagSeparator + strings.Join(node.ServiceTags, srv.tagSeparator) + srv.tagSeparator // If the service address is not empty it should be used instead of the node address // since the service may be registered remotely through a different node var addr string if node.ServiceAddress != "" { addr = net.JoinHostPort(node.ServiceAddress, fmt.Sprintf("%d", node.ServicePort)) } else { addr = net.JoinHostPort(node.Address, fmt.Sprintf("%d", node.ServicePort)) } labels := model.LabelSet{ model.AddressLabel: model.LabelValue(addr), addressLabel: model.LabelValue(node.Address), nodeLabel: model.LabelValue(node.Node), tagsLabel: model.LabelValue(tags), serviceAddressLabel: model.LabelValue(node.ServiceAddress), servicePortLabel: model.LabelValue(strconv.Itoa(node.ServicePort)), serviceIDLabel: model.LabelValue(node.ServiceID), } // Add all key/value pairs from the node's metadata as their own labels for k, v := range node.NodeMeta { name := strutil.SanitizeLabelName(k) labels[metaDataLabel+model.LabelName(name)] = model.LabelValue(v) } tgroup.Targets = append(tgroup.Targets, labels) } // Check context twice to ensure we always catch cancelation. select { case <-ctx.Done(): return default: } select { case <-ctx.Done(): return case ch <- []*targetgroup.Group{&tgroup}: } } } prometheus-2.1.0+ds/discovery/consul/consul_test.go000066400000000000000000000027471323116307200225220ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package consul import ( "testing" ) func TestConfiguredService(t *testing.T) { conf := &SDConfig{ Services: []string{"configuredServiceName"}} consulDiscovery, err := NewDiscovery(conf, nil) if err != nil { t.Errorf("Unexpected error when initialising discovery %v", err) } if !consulDiscovery.shouldWatch("configuredServiceName") { t.Errorf("Expected service %s to be watched", "configuredServiceName") } if consulDiscovery.shouldWatch("nonConfiguredServiceName") { t.Errorf("Expected service %s to not be watched", "nonConfiguredServiceName") } } func TestNonConfiguredService(t *testing.T) { conf := &SDConfig{} consulDiscovery, err := NewDiscovery(conf, nil) if err != nil { t.Errorf("Unexpected error when initialising discovery %v", err) } if !consulDiscovery.shouldWatch("nonConfiguredServiceName") { t.Errorf("Expected service %s to be watched", "nonConfiguredServiceName") } } prometheus-2.1.0+ds/discovery/dns/000077500000000000000000000000001323116307200171005ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/dns/dns.go000066400000000000000000000246631323116307200202260ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dns import ( "context" "fmt" "net" "strings" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( resolvConf = "/etc/resolv.conf" dnsNameLabel = model.MetaLabelPrefix + "dns_name" // Constants for instrumentation. namespace = "prometheus" ) var ( dnsSDLookupsCount = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: namespace, Name: "sd_dns_lookups_total", Help: "The number of DNS-SD lookups.", }) dnsSDLookupFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: namespace, Name: "sd_dns_lookup_failures_total", Help: "The number of DNS-SD lookup failures.", }) // DefaultSDConfig is the default DNS SD configuration. DefaultSDConfig = SDConfig{ RefreshInterval: model.Duration(30 * time.Second), Type: "SRV", } ) // SDConfig is the configuration for DNS based service discovery. type SDConfig struct { Names []string `yaml:"names"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` Type string `yaml:"type"` Port int `yaml:"port"` // Ignored for SRV records // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "dns_sd_config"); err != nil { return err } if len(c.Names) == 0 { return fmt.Errorf("DNS-SD config must contain at least one SRV record name") } switch strings.ToUpper(c.Type) { case "SRV": case "A", "AAAA": if c.Port == 0 { return fmt.Errorf("a port is required in DNS-SD configs for all record types except SRV") } default: return fmt.Errorf("invalid DNS-SD records type %s", c.Type) } return nil } func init() { prometheus.MustRegister(dnsSDLookupFailuresCount) prometheus.MustRegister(dnsSDLookupsCount) } // Discovery periodically performs DNS-SD requests. It implements // the Discoverer interface. type Discovery struct { names []string interval time.Duration port int qtype uint16 logger log.Logger } // NewDiscovery returns a new Discovery which periodically refreshes its targets. func NewDiscovery(conf SDConfig, logger log.Logger) *Discovery { if logger == nil { logger = log.NewNopLogger() } qtype := dns.TypeSRV switch strings.ToUpper(conf.Type) { case "A": qtype = dns.TypeA case "AAAA": qtype = dns.TypeAAAA case "SRV": qtype = dns.TypeSRV } return &Discovery{ names: conf.Names, interval: time.Duration(conf.RefreshInterval), qtype: qtype, port: conf.Port, logger: logger, } } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { ticker := time.NewTicker(d.interval) defer ticker.Stop() // Get an initial set right away. d.refreshAll(ctx, ch) for { select { case <-ticker.C: d.refreshAll(ctx, ch) case <-ctx.Done(): return } } } func (d *Discovery) refreshAll(ctx context.Context, ch chan<- []*targetgroup.Group) { var wg sync.WaitGroup wg.Add(len(d.names)) for _, name := range d.names { go func(n string) { if err := d.refresh(ctx, n, ch); err != nil { level.Error(d.logger).Log("msg", "Error refreshing DNS targets", "err", err) } wg.Done() }(name) } wg.Wait() } func (d *Discovery) refresh(ctx context.Context, name string, ch chan<- []*targetgroup.Group) error { response, err := lookupWithSearchPath(name, d.qtype, d.logger) dnsSDLookupsCount.Inc() if err != nil { dnsSDLookupFailuresCount.Inc() return err } tg := &targetgroup.Group{} hostPort := func(a string, p int) model.LabelValue { return model.LabelValue(net.JoinHostPort(a, fmt.Sprintf("%d", p))) } for _, record := range response.Answer { target := model.LabelValue("") switch addr := record.(type) { case *dns.SRV: // Remove the final dot from rooted DNS names to make them look more usual. addr.Target = strings.TrimRight(addr.Target, ".") target = hostPort(addr.Target, int(addr.Port)) case *dns.A: target = hostPort(addr.A.String(), d.port) case *dns.AAAA: target = hostPort(addr.AAAA.String(), d.port) default: level.Warn(d.logger).Log("msg", "Invalid SRV record", "record", record) continue } tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: target, dnsNameLabel: model.LabelValue(name), }) } tg.Source = name select { case <-ctx.Done(): return ctx.Err() case ch <- []*targetgroup.Group{tg}: } return nil } // lookupWithSearchPath tries to get an answer for various permutations of // the given name, appending the system-configured search path as necessary. // // There are three possible outcomes: // // 1. One of the permutations of the given name is recognised as // "valid" by the DNS, in which case we consider ourselves "done" // and that answer is returned. Note that, due to the way the DNS // handles "name has resource records, but none of the specified type", // the answer received may have an empty set of results. // // 2. All of the permutations of the given name are responded to by one of // the servers in the "nameservers" list with the answer "that name does // not exist" (NXDOMAIN). In that case, it can be considered // pseudo-authoritative that there are no records for that name. // // 3. One or more of the names was responded to by all servers with some // sort of error indication. In that case, we can't know if, in fact, // there are records for the name or not, so whatever state the // configuration is in, we should keep it that way until we know for // sure (by, presumably, all the names getting answers in the future). // // Outcomes 1 and 2 are indicated by a valid response message (possibly an // empty one) and no error. Outcome 3 is indicated by an error return. The // error will be generic-looking, because trying to return all the errors // returned by the combination of all name permutations and servers is a // nightmare. func lookupWithSearchPath(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) { conf, err := dns.ClientConfigFromFile(resolvConf) if err != nil { return nil, fmt.Errorf("could not load resolv.conf: %s", err) } allResponsesValid := true for _, lname := range conf.NameList(name) { response, err := lookupFromAnyServer(lname, qtype, conf, logger) if err != nil { // We can't go home yet, because a later name // may give us a valid, successful answer. However // we can no longer say "this name definitely doesn't // exist", because we did not get that answer for // at least one name. allResponsesValid = false } else if response.Rcode == dns.RcodeSuccess { // Outcome 1: GOLD! return response, nil } } if allResponsesValid { // Outcome 2: everyone says NXDOMAIN, that's good enough for me return &dns.Msg{}, nil } // Outcome 3: boned. return nil, fmt.Errorf("could not resolve %q: all servers responded with errors to at least one search domain", name) } // lookupFromAnyServer uses all configured servers to try and resolve a specific // name. If a viable answer is received from a server, then it is // immediately returned, otherwise the other servers in the config are // tried, and if none of them return a viable answer, an error is returned. // // A "viable answer" is one which indicates either: // // 1. "yes, I know that name, and here are its records of the requested type" // (RCODE==SUCCESS, ANCOUNT > 0); // 2. "yes, I know that name, but it has no records of the requested type" // (RCODE==SUCCESS, ANCOUNT==0); or // 3. "I know that name doesn't exist" (RCODE==NXDOMAIN). // // A non-viable answer is "anything else", which encompasses both various // system-level problems (like network timeouts) and also // valid-but-unexpected DNS responses (SERVFAIL, REFUSED, etc). func lookupFromAnyServer(name string, qtype uint16, conf *dns.ClientConfig, logger log.Logger) (*dns.Msg, error) { client := &dns.Client{} for _, server := range conf.Servers { servAddr := net.JoinHostPort(server, conf.Port) msg, err := askServerForName(name, qtype, client, servAddr, false) if err != nil { level.Warn(logger).Log("msg", "DNS resolution failed", "server", server, "name", name, "err", err) continue } if msg.Rcode == dns.RcodeSuccess || msg.Rcode == dns.RcodeNameError { // We have our answer. Time to go home. return msg, nil } } return nil, fmt.Errorf("could not resolve %s: no servers returned a viable answer", name) } // askServerForName makes a request to a specific DNS server for a specific // name (and qtype). Retries in the event of response truncation, but // otherwise just sends back whatever the server gave, whether that be a // valid-looking response, or an error. func askServerForName(name string, queryType uint16, client *dns.Client, servAddr string, edns bool) (*dns.Msg, error) { msg := &dns.Msg{} msg.SetQuestion(dns.Fqdn(name), queryType) if edns { msg.SetEdns0(dns.DefaultMsgSize, false) } response, _, err := client.Exchange(msg, servAddr) if err == dns.ErrTruncated { if client.Net == "tcp" { return nil, fmt.Errorf("got truncated message on TCP (64kiB limit exceeded?)") } if edns { // Truncated even though EDNS is used client.Net = "tcp" } return askServerForName(name, queryType, client, servAddr, !edns) } if err != nil { return nil, err } if msg.Id != response.Id { return nil, fmt.Errorf("DNS ID mismatch, request: %d, response: %d", msg.Id, response.Id) } return response, nil } prometheus-2.1.0+ds/discovery/ec2/000077500000000000000000000000001323116307200167655ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/ec2/ec2.go000066400000000000000000000202071323116307200177660ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ec2 import ( "context" "fmt" "net" "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/aws/aws-sdk-go/service/ec2" config_util "github.com/prometheus/common/config" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( ec2Label = model.MetaLabelPrefix + "ec2_" ec2LabelAZ = ec2Label + "availability_zone" ec2LabelInstanceID = ec2Label + "instance_id" ec2LabelInstanceState = ec2Label + "instance_state" ec2LabelInstanceType = ec2Label + "instance_type" ec2LabelPublicDNS = ec2Label + "public_dns_name" ec2LabelPublicIP = ec2Label + "public_ip" ec2LabelPrivateIP = ec2Label + "private_ip" ec2LabelSubnetID = ec2Label + "subnet_id" ec2LabelTag = ec2Label + "tag_" ec2LabelVPCID = ec2Label + "vpc_id" subnetSeparator = "," ) var ( ec2SDRefreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_ec2_refresh_failures_total", Help: "The number of EC2-SD scrape failures.", }) ec2SDRefreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_ec2_refresh_duration_seconds", Help: "The duration of a EC2-SD refresh in seconds.", }) // DefaultSDConfig is the default EC2 SD configuration. DefaultSDConfig = SDConfig{ Port: 80, RefreshInterval: model.Duration(60 * time.Second), } ) // SDConfig is the configuration for EC2 based service discovery. type SDConfig struct { Region string `yaml:"region"` AccessKey string `yaml:"access_key,omitempty"` SecretKey config_util.Secret `yaml:"secret_key,omitempty"` Profile string `yaml:"profile,omitempty"` RoleARN string `yaml:"role_arn,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` Port int `yaml:"port"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "ec2_sd_config"); err != nil { return err } if c.Region == "" { sess, err := session.NewSession() if err != nil { return err } metadata := ec2metadata.New(sess) region, err := metadata.Region() if err != nil { return fmt.Errorf("EC2 SD configuration requires a region") } c.Region = region } return nil } func init() { prometheus.MustRegister(ec2SDRefreshFailuresCount) prometheus.MustRegister(ec2SDRefreshDuration) } // Discovery periodically performs EC2-SD requests. It implements // the Discoverer interface. type Discovery struct { aws *aws.Config interval time.Duration profile string roleARN string port int logger log.Logger } // NewDiscovery returns a new EC2Discovery which periodically refreshes its targets. func NewDiscovery(conf *SDConfig, logger log.Logger) *Discovery { creds := credentials.NewStaticCredentials(conf.AccessKey, string(conf.SecretKey), "") if conf.AccessKey == "" && conf.SecretKey == "" { creds = nil } if logger == nil { logger = log.NewNopLogger() } return &Discovery{ aws: &aws.Config{ Region: &conf.Region, Credentials: creds, }, profile: conf.Profile, roleARN: conf.RoleARN, interval: time.Duration(conf.RefreshInterval), port: conf.Port, logger: logger, } } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { ticker := time.NewTicker(d.interval) defer ticker.Stop() // Get an initial set right away. tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refresh failed", "err", err) } else { select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } } for { select { case <-ticker.C: tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refresh failed", "err", err) continue } select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } case <-ctx.Done(): return } } } func (d *Discovery) ec2MetadataAvailable(sess *session.Session) (isAvailable bool) { svc := ec2metadata.New(sess, &aws.Config{ MaxRetries: aws.Int(0), }) isAvailable = svc.Available() return isAvailable } func (d *Discovery) refresh() (tg *targetgroup.Group, err error) { t0 := time.Now() defer func() { ec2SDRefreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { ec2SDRefreshFailuresCount.Inc() } }() sess, err := session.NewSessionWithOptions(session.Options{ Config: *d.aws, Profile: d.profile, }) if err != nil { return nil, fmt.Errorf("could not create aws session: %s", err) } var ec2s *ec2.EC2 if d.roleARN != "" { creds := stscreds.NewCredentials(sess, d.roleARN) ec2s = ec2.New(sess, &aws.Config{Credentials: creds}) } else { if d.aws.Credentials == nil && d.ec2MetadataAvailable(sess) { creds := ec2rolecreds.NewCredentials(sess) ec2s = ec2.New(sess, &aws.Config{Credentials: creds}) } else { ec2s = ec2.New(sess) } } tg = &targetgroup.Group{ Source: *d.aws.Region, } if err = ec2s.DescribeInstancesPages(nil, func(p *ec2.DescribeInstancesOutput, lastPage bool) bool { for _, r := range p.Reservations { for _, inst := range r.Instances { if inst.PrivateIpAddress == nil { continue } labels := model.LabelSet{ ec2LabelInstanceID: model.LabelValue(*inst.InstanceId), } labels[ec2LabelPrivateIP] = model.LabelValue(*inst.PrivateIpAddress) addr := net.JoinHostPort(*inst.PrivateIpAddress, fmt.Sprintf("%d", d.port)) labels[model.AddressLabel] = model.LabelValue(addr) if inst.PublicIpAddress != nil { labels[ec2LabelPublicIP] = model.LabelValue(*inst.PublicIpAddress) labels[ec2LabelPublicDNS] = model.LabelValue(*inst.PublicDnsName) } labels[ec2LabelAZ] = model.LabelValue(*inst.Placement.AvailabilityZone) labels[ec2LabelInstanceState] = model.LabelValue(*inst.State.Name) labels[ec2LabelInstanceType] = model.LabelValue(*inst.InstanceType) if inst.VpcId != nil { labels[ec2LabelVPCID] = model.LabelValue(*inst.VpcId) subnetsMap := make(map[string]struct{}) for _, eni := range inst.NetworkInterfaces { subnetsMap[*eni.SubnetId] = struct{}{} } subnets := []string{} for k := range subnetsMap { subnets = append(subnets, k) } labels[ec2LabelSubnetID] = model.LabelValue( subnetSeparator + strings.Join(subnets, subnetSeparator) + subnetSeparator) } for _, t := range inst.Tags { if t == nil || t.Key == nil || t.Value == nil { continue } name := strutil.SanitizeLabelName(*t.Key) labels[ec2LabelTag+model.LabelName(name)] = model.LabelValue(*t.Value) } tg.Targets = append(tg.Targets, labels) } } return true }); err != nil { return nil, fmt.Errorf("could not describe instances: %s", err) } return tg, nil } prometheus-2.1.0+ds/discovery/file/000077500000000000000000000000001323116307200172335ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/file/file.go000066400000000000000000000254741323116307200205150ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package file import ( "context" "encoding/json" "errors" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strings" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" yaml_util "github.com/prometheus/prometheus/util/yaml" "gopkg.in/fsnotify.v1" "gopkg.in/yaml.v2" ) var ( patFileSDName = regexp.MustCompile(`^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$`) // DefaultSDConfig is the default file SD configuration. DefaultSDConfig = SDConfig{ RefreshInterval: model.Duration(5 * time.Minute), } ) // SDConfig is the configuration for file based discovery. type SDConfig struct { Files []string `yaml:"files"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "file_sd_config"); err != nil { return err } if len(c.Files) == 0 { return fmt.Errorf("file service discovery config must contain at least one path name") } for _, name := range c.Files { if !patFileSDName.MatchString(name) { return fmt.Errorf("path name %q is not valid for file discovery", name) } } return nil } const fileSDFilepathLabel = model.MetaLabelPrefix + "filepath" // TimestampCollector is a Custom Collector for Timestamps of the files. type TimestampCollector struct { Description *prometheus.Desc discoverers map[*Discovery]struct{} lock sync.RWMutex } // Describe method sends the description to the channel. func (t *TimestampCollector) Describe(ch chan<- *prometheus.Desc) { ch <- t.Description } // Collect creates constant metrics for each file with last modified time of the file. func (t *TimestampCollector) Collect(ch chan<- prometheus.Metric) { // New map to dedup filenames. uniqueFiles := make(map[string]float64) t.lock.RLock() for fileSD := range t.discoverers { for filename, timestamp := range fileSD.timestamps { uniqueFiles[filename] = timestamp } } t.lock.RUnlock() for filename, timestamp := range uniqueFiles { ch <- prometheus.MustNewConstMetric( t.Description, prometheus.GaugeValue, timestamp, filename, ) } } func (t *TimestampCollector) addDiscoverer(disc *Discovery) { t.lock.Lock() t.discoverers[disc] = struct{}{} t.lock.Unlock() } func (t *TimestampCollector) removeDiscoverer(disc *Discovery) { t.lock.Lock() delete(t.discoverers, disc) t.lock.Unlock() } // NewTimestampCollector creates a TimestampCollector. func NewTimestampCollector() *TimestampCollector { return &TimestampCollector{ Description: prometheus.NewDesc( "prometheus_sd_file_timestamp", "Timestamp (mtime) of files read by FileSD. Timestamp is set at read time.", []string{"filename"}, nil, ), discoverers: make(map[*Discovery]struct{}), } } var ( fileSDScanDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_file_scan_duration_seconds", Help: "The duration of the File-SD scan in seconds.", }) fileSDReadErrorsCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_file_read_errors_total", Help: "The number of File-SD read errors.", }) fileSDTimeStamp = NewTimestampCollector() ) func init() { prometheus.MustRegister(fileSDScanDuration) prometheus.MustRegister(fileSDReadErrorsCount) prometheus.MustRegister(fileSDTimeStamp) } // Discovery provides service discovery functionality based // on files that contain target groups in JSON or YAML format. Refreshing // happens using file watches and periodic refreshes. type Discovery struct { paths []string watcher *fsnotify.Watcher interval time.Duration timestamps map[string]float64 lock sync.RWMutex // lastRefresh stores which files were found during the last refresh // and how many target groups they contained. // This is used to detect deleted target groups. lastRefresh map[string]int logger log.Logger } // NewDiscovery returns a new file discovery for the given paths. func NewDiscovery(conf *SDConfig, logger log.Logger) *Discovery { if logger == nil { logger = log.NewNopLogger() } disc := &Discovery{ paths: conf.Files, interval: time.Duration(conf.RefreshInterval), timestamps: make(map[string]float64), logger: logger, } fileSDTimeStamp.addDiscoverer(disc) return disc } // listFiles returns a list of all files that match the configured patterns. func (d *Discovery) listFiles() []string { var paths []string for _, p := range d.paths { files, err := filepath.Glob(p) if err != nil { level.Error(d.logger).Log("msg", "Error expanding glob", "glob", p, "err", err) continue } paths = append(paths, files...) } return paths } // watchFiles sets watches on all full paths or directories that were configured for // this file discovery. func (d *Discovery) watchFiles() { if d.watcher == nil { panic("no watcher configured") } for _, p := range d.paths { if idx := strings.LastIndex(p, "/"); idx > -1 { p = p[:idx] } else { p = "./" } if err := d.watcher.Add(p); err != nil { level.Error(d.logger).Log("msg", "Error adding file watch", "path", p, "err", err) } } } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { watcher, err := fsnotify.NewWatcher() if err != nil { level.Error(d.logger).Log("msg", "Error adding file watcher", "err", err) return } d.watcher = watcher defer d.stop() d.refresh(ctx, ch) ticker := time.NewTicker(d.interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case event := <-d.watcher.Events: // fsnotify sometimes sends a bunch of events without name or operation. // It's unclear what they are and why they are sent - filter them out. if len(event.Name) == 0 { break } // Everything but a chmod requires rereading. if event.Op^fsnotify.Chmod == 0 { break } // Changes to a file can spawn various sequences of events with // different combinations of operations. For all practical purposes // this is inaccurate. // The most reliable solution is to reload everything if anything happens. d.refresh(ctx, ch) case <-ticker.C: // Setting a new watch after an update might fail. Make sure we don't lose // those files forever. d.refresh(ctx, ch) case err := <-d.watcher.Errors: if err != nil { level.Error(d.logger).Log("msg", "Error watching file", "err", err) } } } } func (d *Discovery) writeTimestamp(filename string, timestamp float64) { d.lock.Lock() d.timestamps[filename] = timestamp d.lock.Unlock() } func (d *Discovery) deleteTimestamp(filename string) { d.lock.Lock() delete(d.timestamps, filename) d.lock.Unlock() } // stop shuts down the file watcher. func (d *Discovery) stop() { level.Debug(d.logger).Log("msg", "Stopping file discovery...", "paths", d.paths) done := make(chan struct{}) defer close(done) fileSDTimeStamp.removeDiscoverer(d) // Closing the watcher will deadlock unless all events and errors are drained. go func() { for { select { case <-d.watcher.Errors: case <-d.watcher.Events: // Drain all events and errors. case <-done: return } } }() if err := d.watcher.Close(); err != nil { level.Error(d.logger).Log("msg", "Error closing file watcher", "paths", d.paths, "err", err) } level.Debug(d.logger).Log("File discovery stopped", "paths", d.paths) } // refresh reads all files matching the discovery's patterns and sends the respective // updated target groups through the channel. func (d *Discovery) refresh(ctx context.Context, ch chan<- []*targetgroup.Group) { t0 := time.Now() defer func() { fileSDScanDuration.Observe(time.Since(t0).Seconds()) }() ref := map[string]int{} for _, p := range d.listFiles() { tgroups, err := d.readFile(p) if err != nil { fileSDReadErrorsCount.Inc() level.Error(d.logger).Log("msg", "Error reading file", "path", p, "err", err) // Prevent deletion down below. ref[p] = d.lastRefresh[p] continue } select { case ch <- tgroups: case <-ctx.Done(): return } ref[p] = len(tgroups) } // Send empty updates for sources that disappeared. for f, n := range d.lastRefresh { m, ok := ref[f] if !ok || n > m { level.Debug(d.logger).Log("msg", "file_sd refresh found file that should be removed", "file", f) d.deleteTimestamp(f) for i := m; i < n; i++ { select { case ch <- []*targetgroup.Group{{Source: fileSource(f, i)}}: case <-ctx.Done(): return } } } } d.lastRefresh = ref d.watchFiles() } // readFile reads a JSON or YAML list of targets groups from the file, depending on its // file extension. It returns full configuration target groups. func (d *Discovery) readFile(filename string) ([]*targetgroup.Group, error) { fd, err := os.Open(filename) if err != nil { return nil, err } defer fd.Close() content, err := ioutil.ReadAll(fd) if err != nil { return nil, err } info, err := fd.Stat() if err != nil { return nil, err } var targetGroups []*targetgroup.Group switch ext := filepath.Ext(filename); strings.ToLower(ext) { case ".json": if err := json.Unmarshal(content, &targetGroups); err != nil { return nil, err } case ".yml", ".yaml": if err := yaml.Unmarshal(content, &targetGroups); err != nil { return nil, err } default: panic(fmt.Errorf("retrieval.FileDiscovery.readFile: unhandled file extension %q", ext)) } for i, tg := range targetGroups { if tg == nil { err = errors.New("nil target group item found") return nil, err } tg.Source = fileSource(filename, i) if tg.Labels == nil { tg.Labels = model.LabelSet{} } tg.Labels[fileSDFilepathLabel] = model.LabelValue(filename) } d.writeTimestamp(filename, float64(info.ModTime().Unix())) return targetGroups, nil } // fileSource returns a source ID for the i-th target group in the file. func fileSource(filename string, i int) string { return fmt.Sprintf("%s:%d", filename, i) } prometheus-2.1.0+ds/discovery/file/file_test.go000066400000000000000000000110551323116307200215420ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package file import ( "context" "io" "os" "path/filepath" "testing" "time" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" ) const testDir = "fixtures" func TestFileSD(t *testing.T) { defer os.Remove(filepath.Join(testDir, "_test_valid.yml")) defer os.Remove(filepath.Join(testDir, "_test_valid.json")) defer os.Remove(filepath.Join(testDir, "_test_invalid_nil.json")) defer os.Remove(filepath.Join(testDir, "_test_invalid_nil.yml")) testFileSD(t, "valid", ".yml", true) testFileSD(t, "valid", ".json", true) testFileSD(t, "invalid_nil", ".json", false) testFileSD(t, "invalid_nil", ".yml", false) } func testFileSD(t *testing.T, prefix, ext string, expect bool) { // As interval refreshing is more of a fallback, we only want to test // whether file watches work as expected. var conf SDConfig conf.Files = []string{filepath.Join(testDir, "_*"+ext)} conf.RefreshInterval = model.Duration(1 * time.Hour) var ( fsd = NewDiscovery(&conf, nil) ch = make(chan []*targetgroup.Group) ctx, cancel = context.WithCancel(context.Background()) ) go fsd.Run(ctx, ch) select { case <-time.After(25 * time.Millisecond): // Expected. case tgs := <-ch: t.Fatalf("Unexpected target groups in file discovery: %s", tgs) } // To avoid empty group struct sent from the discovery caused by invalid fsnotify updates, // drain the channel until we are ready with the test files. fileReady := make(chan struct{}) drainReady := make(chan struct{}) go func() { for { select { case <-ch: case <-fileReady: close(drainReady) return } } }() newf, err := os.Create(filepath.Join(testDir, "_test_"+prefix+ext)) if err != nil { t.Fatal(err) } defer newf.Close() f, err := os.Open(filepath.Join(testDir, prefix+ext)) if err != nil { t.Fatal(err) } defer f.Close() _, err = io.Copy(newf, f) if err != nil { t.Fatal(err) } // Test file is ready so stop draining the discovery channel. // It contains two target groups. close(fileReady) <-drainReady newf.WriteString(" ") // One last meaningless write to trigger fsnotify and a new loop of the discovery service. timeout := time.After(15 * time.Second) retry: for { select { case <-timeout: if expect { t.Fatalf("Expected new target group but got none") } else { // Invalid type fsd should always break down. break retry } case tgs := <-ch: if !expect { t.Fatalf("Unexpected target groups %s, we expected a failure here.", tgs) } if len(tgs) != 2 { continue retry // Potentially a partial write, just retry. } tg := tgs[0] if _, ok := tg.Labels["foo"]; !ok { t.Fatalf("Label not parsed") } if tg.String() != filepath.Join(testDir, "_test_"+prefix+ext+":0") { t.Fatalf("Unexpected target group %s", tg) } tg = tgs[1] if tg.String() != filepath.Join(testDir, "_test_"+prefix+ext+":1") { t.Fatalf("Unexpected target groups %s", tg) } break retry } } // Based on unknown circumstances, sometimes fsnotify will trigger more events in // some runs (which might be empty, chains of different operations etc.). // We have to drain those (as the target manager would) to avoid deadlocking and must // not try to make sense of it all... drained := make(chan struct{}) go func() { for { select { case tgs := <-ch: // Below we will change the file to a bad syntax. Previously extracted target // groups must not be deleted via sending an empty target group. if len(tgs[0].Targets) == 0 { t.Errorf("Unexpected empty target groups received: %s", tgs) } case <-time.After(500 * time.Millisecond): close(drained) return } } }() newf, err = os.Create(filepath.Join(testDir, "_test.new")) if err != nil { t.Fatal(err) } defer os.Remove(newf.Name()) if _, err := newf.Write([]byte("]gibberish\n][")); err != nil { t.Fatal(err) } newf.Close() os.Rename(newf.Name(), filepath.Join(testDir, "_test_"+prefix+ext)) cancel() <-drained } prometheus-2.1.0+ds/discovery/file/fixtures/000077500000000000000000000000001323116307200211045ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/file/fixtures/invalid_nil.json000066400000000000000000000002131323116307200242630ustar00rootroot00000000000000[ { "targets": ["localhost:9090", "example.org:443"], "labels": { "foo": "bar" } }, null ] prometheus-2.1.0+ds/discovery/file/fixtures/invalid_nil.yml000066400000000000000000000001201323116307200241100ustar00rootroot00000000000000- targets: ['localhost:9090', 'example.org:443'] labels: foo: bar - null prometheus-2.1.0+ds/discovery/file/fixtures/valid.json000066400000000000000000000002021323116307200230700ustar00rootroot00000000000000[ { "targets": ["localhost:9090", "example.org:443"], "labels": { "foo": "bar" } }, { "targets": ["my.domain"] } ] prometheus-2.1.0+ds/discovery/file/fixtures/valid.yml000066400000000000000000000001421323116307200227230ustar00rootroot00000000000000- targets: ['localhost:9090', 'example.org:443'] labels: foo: bar - targets: ['my.domain'] prometheus-2.1.0+ds/discovery/gce/000077500000000000000000000000001323116307200170525ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/gce/gce.go000066400000000000000000000175131323116307200201460ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gce import ( "context" "fmt" "net/http" "strings" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "golang.org/x/oauth2" "golang.org/x/oauth2/google" compute "google.golang.org/api/compute/v1" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( gceLabel = model.MetaLabelPrefix + "gce_" gceLabelProject = gceLabel + "project" gceLabelZone = gceLabel + "zone" gceLabelNetwork = gceLabel + "network" gceLabelSubnetwork = gceLabel + "subnetwork" gceLabelPublicIP = gceLabel + "public_ip" gceLabelPrivateIP = gceLabel + "private_ip" gceLabelInstanceName = gceLabel + "instance_name" gceLabelInstanceStatus = gceLabel + "instance_status" gceLabelTags = gceLabel + "tags" gceLabelMetadata = gceLabel + "metadata_" ) var ( gceSDRefreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_gce_refresh_failures_total", Help: "The number of GCE-SD refresh failures.", }) gceSDRefreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_gce_refresh_duration", Help: "The duration of a GCE-SD refresh in seconds.", }) // DefaultSDConfig is the default GCE SD configuration. DefaultSDConfig = SDConfig{ Port: 80, TagSeparator: ",", RefreshInterval: model.Duration(60 * time.Second), } ) // SDConfig is the configuration for GCE based service discovery. type SDConfig struct { // Project: The Google Cloud Project ID Project string `yaml:"project"` // Zone: The zone of the scrape targets. // If you need to configure multiple zones use multiple gce_sd_configs Zone string `yaml:"zone"` // Filter: Can be used optionally to filter the instance list by other criteria. // Syntax of this filter string is described here in the filter query parameter section: // https://cloud.google.com/compute/docs/reference/latest/instances/list Filter string `yaml:"filter,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` Port int `yaml:"port"` TagSeparator string `yaml:"tag_separator,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "gce_sd_config"); err != nil { return err } if c.Project == "" { return fmt.Errorf("GCE SD configuration requires a project") } if c.Zone == "" { return fmt.Errorf("GCE SD configuration requires a zone") } return nil } func init() { prometheus.MustRegister(gceSDRefreshFailuresCount) prometheus.MustRegister(gceSDRefreshDuration) } // Discovery periodically performs GCE-SD requests. It implements // the Discoverer interface. type Discovery struct { project string zone string filter string client *http.Client svc *compute.Service isvc *compute.InstancesService interval time.Duration port int tagSeparator string logger log.Logger } // NewDiscovery returns a new Discovery which periodically refreshes its targets. func NewDiscovery(conf SDConfig, logger log.Logger) (*Discovery, error) { if logger == nil { logger = log.NewNopLogger() } gd := &Discovery{ project: conf.Project, zone: conf.Zone, filter: conf.Filter, interval: time.Duration(conf.RefreshInterval), port: conf.Port, tagSeparator: conf.TagSeparator, logger: logger, } var err error gd.client, err = google.DefaultClient(oauth2.NoContext, compute.ComputeReadonlyScope) if err != nil { return nil, fmt.Errorf("error setting up communication with GCE service: %s", err) } gd.svc, err = compute.New(gd.client) if err != nil { return nil, fmt.Errorf("error setting up communication with GCE service: %s", err) } gd.isvc = compute.NewInstancesService(gd.svc) return gd, nil } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Get an initial set right away. tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refresh failed", "err", err) } else { select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): } } ticker := time.NewTicker(d.interval) defer ticker.Stop() for { select { case <-ticker.C: tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refresh failed", "err", err) continue } select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): } case <-ctx.Done(): return } } } func (d *Discovery) refresh() (tg *targetgroup.Group, err error) { t0 := time.Now() defer func() { gceSDRefreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { gceSDRefreshFailuresCount.Inc() } }() tg = &targetgroup.Group{ Source: fmt.Sprintf("GCE_%s_%s", d.project, d.zone), } ilc := d.isvc.List(d.project, d.zone) if len(d.filter) > 0 { ilc = ilc.Filter(d.filter) } err = ilc.Pages(nil, func(l *compute.InstanceList) error { for _, inst := range l.Items { if len(inst.NetworkInterfaces) == 0 { continue } labels := model.LabelSet{ gceLabelProject: model.LabelValue(d.project), gceLabelZone: model.LabelValue(inst.Zone), gceLabelInstanceName: model.LabelValue(inst.Name), gceLabelInstanceStatus: model.LabelValue(inst.Status), } priIface := inst.NetworkInterfaces[0] labels[gceLabelNetwork] = model.LabelValue(priIface.Network) labels[gceLabelSubnetwork] = model.LabelValue(priIface.Subnetwork) labels[gceLabelPrivateIP] = model.LabelValue(priIface.NetworkIP) addr := fmt.Sprintf("%s:%d", priIface.NetworkIP, d.port) labels[model.AddressLabel] = model.LabelValue(addr) // Tags in GCE are usually only used for networking rules. if inst.Tags != nil && len(inst.Tags.Items) > 0 { // We surround the separated list with the separator as well. This way regular expressions // in relabeling rules don't have to consider tag positions. tags := d.tagSeparator + strings.Join(inst.Tags.Items, d.tagSeparator) + d.tagSeparator labels[gceLabelTags] = model.LabelValue(tags) } // GCE metadata are key-value pairs for user supplied attributes. if inst.Metadata != nil { for _, i := range inst.Metadata.Items { // Protect against occasional nil pointers. if i.Value == nil { continue } name := strutil.SanitizeLabelName(i.Key) labels[gceLabelMetadata+model.LabelName(name)] = model.LabelValue(*i.Value) } } if len(priIface.AccessConfigs) > 0 { ac := priIface.AccessConfigs[0] if ac.Type == "ONE_TO_ONE_NAT" { labels[gceLabelPublicIP] = model.LabelValue(ac.NatIP) } } tg.Targets = append(tg.Targets, labels) } return nil }) if err != nil { return tg, fmt.Errorf("error retrieving refresh targets from gce: %s", err) } return tg, nil } prometheus-2.1.0+ds/discovery/kubernetes/000077500000000000000000000000001323116307200204635ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/kubernetes/endpoints.go000066400000000000000000000222251323116307200230200ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "net" "strconv" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" apiv1 "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) // Endpoints discovers new endpoint targets. type Endpoints struct { logger log.Logger endpointsInf cache.SharedInformer serviceInf cache.SharedInformer podInf cache.SharedInformer podStore cache.Store endpointsStore cache.Store serviceStore cache.Store } // NewEndpoints returns a new endpoints discovery. func NewEndpoints(l log.Logger, svc, eps, pod cache.SharedInformer) *Endpoints { if l == nil { l = log.NewNopLogger() } ep := &Endpoints{ logger: l, endpointsInf: eps, endpointsStore: eps.GetStore(), serviceInf: svc, serviceStore: svc.GetStore(), podInf: pod, podStore: pod.GetStore(), } return ep } // Run implements the Discoverer interface. func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Send full initial set of endpoint targets. var initial []*targetgroup.Group for _, o := range e.endpointsStore.List() { tg := e.buildEndpoints(o.(*apiv1.Endpoints)) initial = append(initial, tg) } select { case <-ctx.Done(): return case ch <- initial: } // Send target groups for pod updates. send := func(tg *targetgroup.Group) { if tg == nil { return } level.Debug(e.logger).Log("msg", "endpoints update", "tg", fmt.Sprintf("%#v", tg)) select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { eventCount.WithLabelValues("endpoints", "add").Inc() eps, err := convertToEndpoints(o) if err != nil { level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err) return } send(e.buildEndpoints(eps)) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("endpoints", "update").Inc() eps, err := convertToEndpoints(o) if err != nil { level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err) return } send(e.buildEndpoints(eps)) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("endpoints", "delete").Inc() eps, err := convertToEndpoints(o) if err != nil { level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err) return } send(&targetgroup.Group{Source: endpointsSource(eps)}) }, }) serviceUpdate := func(o interface{}) { svc, err := convertToService(o) if err != nil { level.Error(e.logger).Log("msg", "converting to Service object failed", "err", err) return } ep := &apiv1.Endpoints{} ep.Namespace = svc.Namespace ep.Name = svc.Name obj, exists, err := e.endpointsStore.Get(ep) if exists && err != nil { send(e.buildEndpoints(obj.(*apiv1.Endpoints))) } if err != nil { level.Error(e.logger).Log("msg", "retrieving endpoints failed", "err", err) } } e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ // TODO(fabxc): potentially remove add and delete event handlers. Those should // be triggered via the endpoint handlers already. AddFunc: func(o interface{}) { eventCount.WithLabelValues("service", "add").Inc() serviceUpdate(o) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("service", "update").Inc() serviceUpdate(o) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("service", "delete").Inc() serviceUpdate(o) }, }) // Block until the target provider is explicitly canceled. <-ctx.Done() } func convertToEndpoints(o interface{}) (*apiv1.Endpoints, error) { endpoints, ok := o.(*apiv1.Endpoints) if ok { return endpoints, nil } deletedState, ok := o.(cache.DeletedFinalStateUnknown) if !ok { return nil, fmt.Errorf("Received unexpected object: %v", o) } endpoints, ok = deletedState.Obj.(*apiv1.Endpoints) if !ok { return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Endpoints object: %v", deletedState.Obj) } return endpoints, nil } func endpointsSource(ep *apiv1.Endpoints) string { return "endpoints/" + ep.ObjectMeta.Namespace + "/" + ep.ObjectMeta.Name } const ( endpointsNameLabel = metaLabelPrefix + "endpoints_name" endpointReadyLabel = metaLabelPrefix + "endpoint_ready" endpointPortNameLabel = metaLabelPrefix + "endpoint_port_name" endpointPortProtocolLabel = metaLabelPrefix + "endpoint_port_protocol" ) func (e *Endpoints) buildEndpoints(eps *apiv1.Endpoints) *targetgroup.Group { if len(eps.Subsets) == 0 { return nil } tg := &targetgroup.Group{ Source: endpointsSource(eps), } tg.Labels = model.LabelSet{ namespaceLabel: lv(eps.Namespace), endpointsNameLabel: lv(eps.Name), } e.addServiceLabels(eps.Namespace, eps.Name, tg) type podEntry struct { pod *apiv1.Pod servicePorts []apiv1.EndpointPort } seenPods := map[string]*podEntry{} add := func(addr apiv1.EndpointAddress, port apiv1.EndpointPort, ready string) { a := net.JoinHostPort(addr.IP, strconv.FormatUint(uint64(port.Port), 10)) target := model.LabelSet{ model.AddressLabel: lv(a), endpointPortNameLabel: lv(port.Name), endpointPortProtocolLabel: lv(string(port.Protocol)), endpointReadyLabel: lv(ready), } pod := e.resolvePodRef(addr.TargetRef) if pod == nil { // This target is not a Pod, so don't continue with Pod specific logic. tg.Targets = append(tg.Targets, target) return } s := pod.Namespace + "/" + pod.Name sp, ok := seenPods[s] if !ok { sp = &podEntry{pod: pod} seenPods[s] = sp } // Attach standard pod labels. target = target.Merge(podLabels(pod)) // Attach potential container port labels matching the endpoint port. for _, c := range pod.Spec.Containers { for _, cport := range c.Ports { if port.Port == cport.ContainerPort { ports := strconv.FormatUint(uint64(port.Port), 10) target[podContainerNameLabel] = lv(c.Name) target[podContainerPortNameLabel] = lv(cport.Name) target[podContainerPortNumberLabel] = lv(ports) target[podContainerPortProtocolLabel] = lv(string(port.Protocol)) break } } } // Add service port so we know that we have already generated a target // for it. sp.servicePorts = append(sp.servicePorts, port) tg.Targets = append(tg.Targets, target) } for _, ss := range eps.Subsets { for _, port := range ss.Ports { for _, addr := range ss.Addresses { add(addr, port, "true") } // Although this generates the same target again, as it was generated in // the loop above, it causes the ready meta label to be overridden. for _, addr := range ss.NotReadyAddresses { add(addr, port, "false") } } } // For all seen pods, check all container ports. If they were not covered // by one of the service endpoints, generate targets for them. for _, pe := range seenPods { for _, c := range pe.pod.Spec.Containers { for _, cport := range c.Ports { hasSeenPort := func() bool { for _, eport := range pe.servicePorts { if cport.ContainerPort == eport.Port { return true } } return false } if hasSeenPort() { continue } a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10)) ports := strconv.FormatUint(uint64(cport.ContainerPort), 10) target := model.LabelSet{ model.AddressLabel: lv(a), podContainerNameLabel: lv(c.Name), podContainerPortNameLabel: lv(cport.Name), podContainerPortNumberLabel: lv(ports), podContainerPortProtocolLabel: lv(string(cport.Protocol)), } tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod))) } } } return tg } func (e *Endpoints) resolvePodRef(ref *apiv1.ObjectReference) *apiv1.Pod { if ref == nil || ref.Kind != "Pod" { return nil } p := &apiv1.Pod{} p.Namespace = ref.Namespace p.Name = ref.Name obj, exists, err := e.podStore.Get(p) if err != nil || !exists { return nil } if err != nil { level.Error(e.logger).Log("msg", "resolving pod ref failed", "err", err) } return obj.(*apiv1.Pod) } func (e *Endpoints) addServiceLabels(ns, name string, tg *targetgroup.Group) { svc := &apiv1.Service{} svc.Namespace = ns svc.Name = name obj, exists, err := e.serviceStore.Get(svc) if !exists || err != nil { return } if err != nil { level.Error(e.logger).Log("msg", "retrieving service failed", "err", err) } svc = obj.(*apiv1.Service) tg.Labels = tg.Labels.Merge(serviceLabels(svc)) } prometheus-2.1.0+ds/discovery/kubernetes/endpoints_test.go000066400000000000000000000221221323116307200240530ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "testing" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) func endpointsStoreKeyFunc(obj interface{}) (string, error) { return obj.(*v1.Endpoints).ObjectMeta.Name, nil } func newFakeEndpointsInformer() *fakeInformer { return newFakeInformer(endpointsStoreKeyFunc) } func makeTestEndpointsDiscovery() (*Endpoints, *fakeInformer, *fakeInformer, *fakeInformer) { svc := newFakeServiceInformer() eps := newFakeEndpointsInformer() pod := newFakePodInformer() return NewEndpoints(nil, svc, eps, pod), svc, eps, pod } func makeEndpoints() *v1.Endpoints { return &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "testendpoints", Namespace: "default", }, Subsets: []v1.EndpointSubset{ { Addresses: []v1.EndpointAddress{ { IP: "1.2.3.4", }, }, Ports: []v1.EndpointPort{ { Name: "testport", Port: 9000, Protocol: v1.ProtocolTCP, }, }, }, { Addresses: []v1.EndpointAddress{ { IP: "2.3.4.5", }, }, NotReadyAddresses: []v1.EndpointAddress{ { IP: "2.3.4.5", }, }, Ports: []v1.EndpointPort{ { Name: "testport", Port: 9001, Protocol: v1.ProtocolTCP, }, }, }, }, } } func TestEndpointsDiscoveryInitial(t *testing.T) { n, _, eps, _ := makeTestEndpointsDiscovery() eps.GetStore().Add(makeEndpoints()) k8sDiscoveryTest{ discovery: n, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "true", }, { "__address__": "2.3.4.5:9001", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "true", }, { "__address__": "2.3.4.5:9001", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "false", }, }, Labels: model.LabelSet{ "__meta_kubernetes_namespace": "default", "__meta_kubernetes_endpoints_name": "testendpoints", }, Source: "endpoints/default/testendpoints", }, }, }.Run(t) } func TestEndpointsDiscoveryAdd(t *testing.T) { n, _, eps, pods := makeTestEndpointsDiscovery() pods.GetStore().Add(&v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "testpod", Namespace: "default", UID: types.UID("deadbeef"), }, Spec: v1.PodSpec{ NodeName: "testnode", Containers: []v1.Container{ { Name: "c1", Ports: []v1.ContainerPort{ { Name: "mainport", ContainerPort: 9000, Protocol: v1.ProtocolTCP, }, }, }, { Name: "c2", Ports: []v1.ContainerPort{ { Name: "sideport", ContainerPort: 9001, Protocol: v1.ProtocolTCP, }, }, }, }, }, Status: v1.PodStatus{ HostIP: "2.3.4.5", PodIP: "1.2.3.4", }, }) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { eps.Add( &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "testendpoints", Namespace: "default", }, Subsets: []v1.EndpointSubset{ { Addresses: []v1.EndpointAddress{ { IP: "4.3.2.1", TargetRef: &v1.ObjectReference{ Kind: "Pod", Name: "testpod", Namespace: "default", }, }, }, Ports: []v1.EndpointPort{ { Name: "testport", Port: 9000, Protocol: v1.ProtocolTCP, }, }, }, }, }, ) }() }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "4.3.2.1:9000", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "true", "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_ready": "unknown", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_container_name": "c1", "__meta_kubernetes_pod_container_port_name": "mainport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", "__meta_kubernetes_pod_uid": "deadbeef", }, { "__address__": "1.2.3.4:9001", "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_ready": "unknown", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_container_name": "c2", "__meta_kubernetes_pod_container_port_name": "sideport", "__meta_kubernetes_pod_container_port_number": "9001", "__meta_kubernetes_pod_container_port_protocol": "TCP", "__meta_kubernetes_pod_uid": "deadbeef", }, }, Labels: model.LabelSet{ "__meta_kubernetes_endpoints_name": "testendpoints", "__meta_kubernetes_namespace": "default", }, Source: "endpoints/default/testendpoints", }, }, }.Run(t) } func TestEndpointsDiscoveryDelete(t *testing.T) { n, _, eps, _ := makeTestEndpointsDiscovery() eps.GetStore().Add(makeEndpoints()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { eps.Delete(makeEndpoints()) }() }, expectedRes: []*targetgroup.Group{ { Source: "endpoints/default/testendpoints", }, }, }.Run(t) } func TestEndpointsDiscoveryDeleteUnknownCacheState(t *testing.T) { n, _, eps, _ := makeTestEndpointsDiscovery() eps.GetStore().Add(makeEndpoints()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { eps.Delete(cache.DeletedFinalStateUnknown{Obj: makeEndpoints()}) }() }, expectedRes: []*targetgroup.Group{ { Source: "endpoints/default/testendpoints", }, }, }.Run(t) } func TestEndpointsDiscoveryUpdate(t *testing.T) { n, _, eps, _ := makeTestEndpointsDiscovery() eps.GetStore().Add(makeEndpoints()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { eps.Update(&v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "testendpoints", Namespace: "default", }, Subsets: []v1.EndpointSubset{ { Addresses: []v1.EndpointAddress{ { IP: "1.2.3.4", }, }, Ports: []v1.EndpointPort{ { Name: "testport", Port: 9000, Protocol: v1.ProtocolTCP, }, }, }, { Addresses: []v1.EndpointAddress{ { IP: "2.3.4.5", }, }, Ports: []v1.EndpointPort{ { Name: "testport", Port: 9001, Protocol: v1.ProtocolTCP, }, }, }, }, }) }() }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "true", }, { "__address__": "2.3.4.5:9001", "__meta_kubernetes_endpoint_port_name": "testport", "__meta_kubernetes_endpoint_port_protocol": "TCP", "__meta_kubernetes_endpoint_ready": "true", }, }, Labels: model.LabelSet{ "__meta_kubernetes_namespace": "default", "__meta_kubernetes_endpoints_name": "testendpoints", }, Source: "endpoints/default/testendpoints", }, }, }.Run(t) } prometheus-2.1.0+ds/discovery/kubernetes/ingress.go000066400000000000000000000121511323116307200224640ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" "k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/tools/cache" ) // Ingress implements discovery of Kubernetes ingresss. type Ingress struct { logger log.Logger informer cache.SharedInformer store cache.Store } // NewIngress returns a new ingress discovery. func NewIngress(l log.Logger, inf cache.SharedInformer) *Ingress { return &Ingress{logger: l, informer: inf, store: inf.GetStore()} } // Run implements the Discoverer interface. func (s *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Send full initial set of pod targets. var initial []*targetgroup.Group for _, o := range s.store.List() { tg := s.buildIngress(o.(*v1beta1.Ingress)) initial = append(initial, tg) } select { case <-ctx.Done(): return case ch <- initial: } // Send target groups for ingress updates. send := func(tg *targetgroup.Group) { select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { eventCount.WithLabelValues("ingress", "add").Inc() ingress, err := convertToIngress(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error()) return } send(s.buildIngress(ingress)) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("ingress", "delete").Inc() ingress, err := convertToIngress(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error()) return } send(&targetgroup.Group{Source: ingressSource(ingress)}) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("ingress", "update").Inc() ingress, err := convertToIngress(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error()) return } send(s.buildIngress(ingress)) }, }) // Block until the target provider is explicitly canceled. <-ctx.Done() } func convertToIngress(o interface{}) (*v1beta1.Ingress, error) { ingress, ok := o.(*v1beta1.Ingress) if ok { return ingress, nil } deletedState, ok := o.(cache.DeletedFinalStateUnknown) if !ok { return nil, fmt.Errorf("Received unexpected object: %v", o) } ingress, ok = deletedState.Obj.(*v1beta1.Ingress) if !ok { return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Ingress object: %v", deletedState.Obj) } return ingress, nil } func ingressSource(s *v1beta1.Ingress) string { return "ingress/" + s.Namespace + "/" + s.Name } const ( ingressNameLabel = metaLabelPrefix + "ingress_name" ingressLabelPrefix = metaLabelPrefix + "ingress_label_" ingressAnnotationPrefix = metaLabelPrefix + "ingress_annotation_" ingressSchemeLabel = metaLabelPrefix + "ingress_scheme" ingressHostLabel = metaLabelPrefix + "ingress_host" ingressPathLabel = metaLabelPrefix + "ingress_path" ) func ingressLabels(ingress *v1beta1.Ingress) model.LabelSet { ls := make(model.LabelSet, len(ingress.Labels)+len(ingress.Annotations)+2) ls[ingressNameLabel] = lv(ingress.Name) ls[namespaceLabel] = lv(ingress.Namespace) for k, v := range ingress.Labels { ln := strutil.SanitizeLabelName(ingressLabelPrefix + k) ls[model.LabelName(ln)] = lv(v) } for k, v := range ingress.Annotations { ln := strutil.SanitizeLabelName(ingressAnnotationPrefix + k) ls[model.LabelName(ln)] = lv(v) } return ls } func pathsFromIngressRule(rv *v1beta1.IngressRuleValue) []string { if rv.HTTP == nil { return []string{"/"} } paths := make([]string, len(rv.HTTP.Paths)) for n, p := range rv.HTTP.Paths { path := p.Path if path == "" { path = "/" } paths[n] = path } return paths } func (s *Ingress) buildIngress(ingress *v1beta1.Ingress) *targetgroup.Group { tg := &targetgroup.Group{ Source: ingressSource(ingress), } tg.Labels = ingressLabels(ingress) schema := "http" if ingress.Spec.TLS != nil { schema = "https" } for _, rule := range ingress.Spec.Rules { paths := pathsFromIngressRule(&rule.IngressRuleValue) for _, path := range paths { tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: lv(rule.Host), ingressSchemeLabel: lv(schema), ingressHostLabel: lv(rule.Host), ingressPathLabel: lv(path), }) } } return tg } prometheus-2.1.0+ds/discovery/kubernetes/ingress_test.go000066400000000000000000000075201323116307200235270ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "testing" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/pkg/apis/extensions/v1beta1" ) func ingressStoreKeyFunc(obj interface{}) (string, error) { return obj.(*v1beta1.Ingress).ObjectMeta.Name, nil } func newFakeIngressInformer() *fakeInformer { return newFakeInformer(ingressStoreKeyFunc) } func makeTestIngressDiscovery() (*Ingress, *fakeInformer) { i := newFakeIngressInformer() return NewIngress(nil, i), i } func makeIngress(tls []v1beta1.IngressTLS) *v1beta1.Ingress { return &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "testingress", Namespace: "default", Labels: map[string]string{"testlabel": "testvalue"}, Annotations: map[string]string{"testannotation": "testannotationvalue"}, }, Spec: v1beta1.IngressSpec{ TLS: tls, Rules: []v1beta1.IngressRule{ { Host: "example.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{ {Path: "/"}, {Path: "/foo"}, }, }, }, }, { // No backend config, ignored Host: "nobackend.example.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{}, }, }, { Host: "test.example.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{{}}, }, }, }, }, }, } } func expectedTargetGroups(tls bool) []*targetgroup.Group { scheme := "http" if tls { scheme = "https" } return []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_ingress_scheme": lv(scheme), "__meta_kubernetes_ingress_host": "example.com", "__meta_kubernetes_ingress_path": "/", "__address__": "example.com", }, { "__meta_kubernetes_ingress_scheme": lv(scheme), "__meta_kubernetes_ingress_host": "example.com", "__meta_kubernetes_ingress_path": "/foo", "__address__": "example.com", }, { "__meta_kubernetes_ingress_scheme": lv(scheme), "__meta_kubernetes_ingress_host": "test.example.com", "__address__": "test.example.com", "__meta_kubernetes_ingress_path": "/", }, }, Labels: model.LabelSet{ "__meta_kubernetes_ingress_name": "testingress", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_ingress_label_testlabel": "testvalue", "__meta_kubernetes_ingress_annotation_testannotation": "testannotationvalue", }, Source: "ingress/default/testingress", }, } } func TestIngressDiscoveryInitial(t *testing.T) { n, i := makeTestIngressDiscovery() i.GetStore().Add(makeIngress(nil)) k8sDiscoveryTest{ discovery: n, expectedInitial: expectedTargetGroups(false), }.Run(t) } func TestIngressDiscoveryInitialTLS(t *testing.T) { n, i := makeTestIngressDiscovery() i.GetStore().Add(makeIngress([]v1beta1.IngressTLS{{}})) k8sDiscoveryTest{ discovery: n, expectedInitial: expectedTargetGroups(true), }.Run(t) } prometheus-2.1.0+ds/discovery/kubernetes/kubernetes.go000066400000000000000000000254151323116307200231700ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "io/ioutil" "sync" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" yaml_util "github.com/prometheus/prometheus/util/yaml" "k8s.io/client-go/kubernetes" "k8s.io/client-go/pkg/api" apiv1 "k8s.io/client-go/pkg/api/v1" extensionsv1beta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" ) const ( // kubernetesMetaLabelPrefix is the meta prefix used for all meta labels. // in this discovery. metaLabelPrefix = model.MetaLabelPrefix + "kubernetes_" namespaceLabel = metaLabelPrefix + "namespace" ) var ( eventCount = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "prometheus_sd_kubernetes_events_total", Help: "The number of Kubernetes events handled.", }, []string{"role", "event"}, ) // DefaultSDConfig is the default Kubernetes SD configuration DefaultSDConfig = SDConfig{} ) // Role is role of the service in Kubernetes. type Role string // The valid options for Role. const ( RoleNode Role = "node" RolePod Role = "pod" RoleService Role = "service" RoleEndpoint Role = "endpoints" RoleIngress Role = "ingress" ) // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*string)(c)); err != nil { return err } switch *c { case RoleNode, RolePod, RoleService, RoleEndpoint, RoleIngress: return nil default: return fmt.Errorf("Unknown Kubernetes SD role %q", *c) } } // SDConfig is the configuration for Kubernetes service discovery. type SDConfig struct { APIServer config_util.URL `yaml:"api_server"` Role Role `yaml:"role"` BasicAuth *config_util.BasicAuth `yaml:"basic_auth,omitempty"` BearerToken config_util.Secret `yaml:"bearer_token,omitempty"` BearerTokenFile string `yaml:"bearer_token_file,omitempty"` TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"` NamespaceDiscovery NamespaceDiscovery `yaml:"namespaces"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = SDConfig{} type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "kubernetes_sd_config"); err != nil { return err } if c.Role == "" { return fmt.Errorf("role missing (one of: pod, service, endpoints, node)") } if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 { return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured") } if c.BasicAuth != nil && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { return fmt.Errorf("at most one of basic_auth, bearer_token & bearer_token_file must be configured") } if c.APIServer.URL == nil && (c.BasicAuth != nil || c.BearerToken != "" || c.BearerTokenFile != "" || c.TLSConfig.CAFile != "" || c.TLSConfig.CertFile != "" || c.TLSConfig.KeyFile != "") { return fmt.Errorf("to use custom authentication please provide the 'api_server' URL explicitly") } return nil } // NamespaceDiscovery is the configuration for discovering // Kubernetes namespaces. type NamespaceDiscovery struct { Names []string `yaml:"names"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *NamespaceDiscovery) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = NamespaceDiscovery{} type plain NamespaceDiscovery err := unmarshal((*plain)(c)) if err != nil { return err } return yaml_util.CheckOverflow(c.XXX, "namespaces") } func init() { prometheus.MustRegister(eventCount) // Initialize metric vectors. for _, role := range []string{"endpoints", "node", "pod", "service"} { for _, evt := range []string{"add", "delete", "update"} { eventCount.WithLabelValues(role, evt) } } } // Discovery implements the Discoverer interface for discovering // targets from Kubernetes. type Discovery struct { client kubernetes.Interface role Role logger log.Logger namespaceDiscovery *NamespaceDiscovery } func (d *Discovery) getNamespaces() []string { namespaces := d.namespaceDiscovery.Names if len(namespaces) == 0 { namespaces = []string{api.NamespaceAll} } return namespaces } // New creates a new Kubernetes discovery for the given role. func New(l log.Logger, conf *SDConfig) (*Discovery, error) { if l == nil { l = log.NewNopLogger() } var ( kcfg *rest.Config err error ) if conf.APIServer.URL == nil { // Use the Kubernetes provided pod service account // as described in https://kubernetes.io/docs/admin/service-accounts-admin/ kcfg, err = rest.InClusterConfig() if err != nil { return nil, err } // Because the handling of configuration parameters changes // we should inform the user when their currently configured values // will be ignored due to precedence of InClusterConfig level.Info(l).Log("msg", "Using pod service account via in-cluster config") if conf.TLSConfig.CAFile != "" { level.Warn(l).Log("msg", "Configured TLS CA file is ignored when using pod service account") } if conf.TLSConfig.CertFile != "" || conf.TLSConfig.KeyFile != "" { level.Warn(l).Log("msg", "Configured TLS client certificate is ignored when using pod service account") } if conf.BearerToken != "" { level.Warn(l).Log("msg", "Configured auth token is ignored when using pod service account") } if conf.BasicAuth != nil { level.Warn(l).Log("msg", "Configured basic authentication credentials are ignored when using pod service account") } } else { kcfg = &rest.Config{ Host: conf.APIServer.String(), TLSClientConfig: rest.TLSClientConfig{ CAFile: conf.TLSConfig.CAFile, CertFile: conf.TLSConfig.CertFile, KeyFile: conf.TLSConfig.KeyFile, Insecure: conf.TLSConfig.InsecureSkipVerify, }, } token := string(conf.BearerToken) if conf.BearerTokenFile != "" { bf, err := ioutil.ReadFile(conf.BearerTokenFile) if err != nil { return nil, err } token = string(bf) } kcfg.BearerToken = token if conf.BasicAuth != nil { kcfg.Username = conf.BasicAuth.Username kcfg.Password = string(conf.BasicAuth.Password) } } kcfg.UserAgent = "prometheus/discovery" c, err := kubernetes.NewForConfig(kcfg) if err != nil { return nil, err } return &Discovery{ client: c, logger: l, role: conf.Role, namespaceDiscovery: &conf.NamespaceDiscovery, }, nil } const resyncPeriod = 10 * time.Minute // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { rclient := d.client.Core().RESTClient() reclient := d.client.Extensions().RESTClient() namespaces := d.getNamespaces() switch d.role { case "endpoints": var wg sync.WaitGroup for _, namespace := range namespaces { elw := cache.NewListWatchFromClient(rclient, "endpoints", namespace, nil) slw := cache.NewListWatchFromClient(rclient, "services", namespace, nil) plw := cache.NewListWatchFromClient(rclient, "pods", namespace, nil) eps := NewEndpoints( log.With(d.logger, "role", "endpoint"), cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod), cache.NewSharedInformer(elw, &apiv1.Endpoints{}, resyncPeriod), cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod), ) go eps.endpointsInf.Run(ctx.Done()) go eps.serviceInf.Run(ctx.Done()) go eps.podInf.Run(ctx.Done()) for !eps.serviceInf.HasSynced() { time.Sleep(100 * time.Millisecond) } for !eps.endpointsInf.HasSynced() { time.Sleep(100 * time.Millisecond) } for !eps.podInf.HasSynced() { time.Sleep(100 * time.Millisecond) } wg.Add(1) go func() { defer wg.Done() eps.Run(ctx, ch) }() } wg.Wait() case "pod": var wg sync.WaitGroup for _, namespace := range namespaces { plw := cache.NewListWatchFromClient(rclient, "pods", namespace, nil) pod := NewPod( log.With(d.logger, "role", "pod"), cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod), ) go pod.informer.Run(ctx.Done()) for !pod.informer.HasSynced() { time.Sleep(100 * time.Millisecond) } wg.Add(1) go func() { defer wg.Done() pod.Run(ctx, ch) }() } wg.Wait() case "service": var wg sync.WaitGroup for _, namespace := range namespaces { slw := cache.NewListWatchFromClient(rclient, "services", namespace, nil) svc := NewService( log.With(d.logger, "role", "service"), cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod), ) go svc.informer.Run(ctx.Done()) for !svc.informer.HasSynced() { time.Sleep(100 * time.Millisecond) } wg.Add(1) go func() { defer wg.Done() svc.Run(ctx, ch) }() } wg.Wait() case "ingress": var wg sync.WaitGroup for _, namespace := range namespaces { ilw := cache.NewListWatchFromClient(reclient, "ingresses", namespace, nil) ingress := NewIngress( log.With(d.logger, "role", "ingress"), cache.NewSharedInformer(ilw, &extensionsv1beta1.Ingress{}, resyncPeriod), ) go ingress.informer.Run(ctx.Done()) for !ingress.informer.HasSynced() { time.Sleep(100 * time.Millisecond) } wg.Add(1) go func() { defer wg.Done() ingress.Run(ctx, ch) }() } wg.Wait() case "node": nlw := cache.NewListWatchFromClient(rclient, "nodes", api.NamespaceAll, nil) node := NewNode( log.With(d.logger, "role", "node"), cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncPeriod), ) go node.informer.Run(ctx.Done()) for !node.informer.HasSynced() { time.Sleep(100 * time.Millisecond) } node.Run(ctx, ch) default: level.Error(d.logger).Log("msg", "unknown Kubernetes discovery kind", "role", d.role) } <-ctx.Done() } func lv(s string) model.LabelValue { return model.LabelValue(s) } prometheus-2.1.0+ds/discovery/kubernetes/node.go000066400000000000000000000126271323116307200217470ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "net" "strconv" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" "k8s.io/client-go/pkg/api" apiv1 "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) // Node discovers Kubernetes nodes. type Node struct { logger log.Logger informer cache.SharedInformer store cache.Store } // NewNode returns a new node discovery. func NewNode(l log.Logger, inf cache.SharedInformer) *Node { if l == nil { l = log.NewNopLogger() } return &Node{logger: l, informer: inf, store: inf.GetStore()} } // Run implements the Discoverer interface. func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Send full initial set of pod targets. var initial []*targetgroup.Group for _, o := range n.store.List() { tg := n.buildNode(o.(*apiv1.Node)) initial = append(initial, tg) } select { case <-ctx.Done(): return case ch <- initial: } // Send target groups for service updates. send := func(tg *targetgroup.Group) { select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { eventCount.WithLabelValues("node", "add").Inc() node, err := convertToNode(o) if err != nil { level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err) return } send(n.buildNode(node)) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("node", "delete").Inc() node, err := convertToNode(o) if err != nil { level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err) return } send(&targetgroup.Group{Source: nodeSource(node)}) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("node", "update").Inc() node, err := convertToNode(o) if err != nil { level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err) return } send(n.buildNode(node)) }, }) // Block until the target provider is explicitly canceled. <-ctx.Done() } func convertToNode(o interface{}) (*apiv1.Node, error) { node, ok := o.(*apiv1.Node) if ok { return node, nil } deletedState, ok := o.(cache.DeletedFinalStateUnknown) if !ok { return nil, fmt.Errorf("Received unexpected object: %v", o) } node, ok = deletedState.Obj.(*apiv1.Node) if !ok { return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Node object: %v", deletedState.Obj) } return node, nil } func nodeSource(n *apiv1.Node) string { return "node/" + n.Name } const ( nodeNameLabel = metaLabelPrefix + "node_name" nodeLabelPrefix = metaLabelPrefix + "node_label_" nodeAnnotationPrefix = metaLabelPrefix + "node_annotation_" nodeAddressPrefix = metaLabelPrefix + "node_address_" ) func nodeLabels(n *apiv1.Node) model.LabelSet { ls := make(model.LabelSet, len(n.Labels)+len(n.Annotations)+1) ls[nodeNameLabel] = lv(n.Name) for k, v := range n.Labels { ln := strutil.SanitizeLabelName(nodeLabelPrefix + k) ls[model.LabelName(ln)] = lv(v) } for k, v := range n.Annotations { ln := strutil.SanitizeLabelName(nodeAnnotationPrefix + k) ls[model.LabelName(ln)] = lv(v) } return ls } func (n *Node) buildNode(node *apiv1.Node) *targetgroup.Group { tg := &targetgroup.Group{ Source: nodeSource(node), } tg.Labels = nodeLabels(node) addr, addrMap, err := nodeAddress(node) if err != nil { level.Warn(n.logger).Log("msg", "No node address found", "err", err) return nil } addr = net.JoinHostPort(addr, strconv.FormatInt(int64(node.Status.DaemonEndpoints.KubeletEndpoint.Port), 10)) t := model.LabelSet{ model.AddressLabel: lv(addr), model.InstanceLabel: lv(node.Name), } for ty, a := range addrMap { ln := strutil.SanitizeLabelName(nodeAddressPrefix + string(ty)) t[model.LabelName(ln)] = lv(a[0]) } tg.Targets = append(tg.Targets, t) return tg } // nodeAddresses returns the provided node's address, based on the priority: // 1. NodeInternalIP // 2. NodeExternalIP // 3. NodeLegacyHostIP // 3. NodeHostName // // Derived from k8s.io/kubernetes/pkg/util/node/node.go func nodeAddress(node *apiv1.Node) (string, map[apiv1.NodeAddressType][]string, error) { m := map[apiv1.NodeAddressType][]string{} for _, a := range node.Status.Addresses { m[a.Type] = append(m[a.Type], a.Address) } if addresses, ok := m[apiv1.NodeInternalIP]; ok { return addresses[0], m, nil } if addresses, ok := m[apiv1.NodeExternalIP]; ok { return addresses[0], m, nil } if addresses, ok := m[apiv1.NodeAddressType(api.NodeLegacyHostIP)]; ok { return addresses[0], m, nil } if addresses, ok := m[apiv1.NodeHostName]; ok { return addresses[0], m, nil } return "", m, fmt.Errorf("host address unknown") } prometheus-2.1.0+ds/discovery/kubernetes/node_test.go000066400000000000000000000201161323116307200227760ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "encoding/json" "fmt" "sync" "testing" "time" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) type fakeInformer struct { store cache.Store handlers []cache.ResourceEventHandler blockDeltas sync.Mutex } func newFakeInformer(f func(obj interface{}) (string, error)) *fakeInformer { i := &fakeInformer{ store: cache.NewStore(f), } // We want to make sure that all delta events (Add/Update/Delete) are blocked // until our handlers to test have been added. i.blockDeltas.Lock() return i } func (i *fakeInformer) AddEventHandler(h cache.ResourceEventHandler) { i.handlers = append(i.handlers, h) // Only now that there is a registered handler, we are able to handle deltas. i.blockDeltas.Unlock() } func (i *fakeInformer) AddEventHandlerWithResyncPeriod(h cache.ResourceEventHandler, _ time.Duration) { i.AddEventHandler(h) } func (i *fakeInformer) GetStore() cache.Store { return i.store } func (i *fakeInformer) GetController() cache.Controller { return nil } func (i *fakeInformer) Run(stopCh <-chan struct{}) { return } func (i *fakeInformer) HasSynced() bool { return true } func (i *fakeInformer) LastSyncResourceVersion() string { return "0" } func (i *fakeInformer) Add(obj interface{}) { i.blockDeltas.Lock() defer i.blockDeltas.Unlock() for _, h := range i.handlers { h.OnAdd(obj) } } func (i *fakeInformer) Delete(obj interface{}) { i.blockDeltas.Lock() defer i.blockDeltas.Unlock() for _, h := range i.handlers { h.OnDelete(obj) } } func (i *fakeInformer) Update(obj interface{}) { i.blockDeltas.Lock() defer i.blockDeltas.Unlock() for _, h := range i.handlers { h.OnUpdate(nil, obj) } } type discoverer interface { Run(ctx context.Context, up chan<- []*targetgroup.Group) } type k8sDiscoveryTest struct { discovery discoverer afterStart func() expectedInitial []*targetgroup.Group expectedRes []*targetgroup.Group } func (d k8sDiscoveryTest) Run(t *testing.T) { ch := make(chan []*targetgroup.Group) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10) defer cancel() go func() { d.discovery.Run(ctx, ch) }() initialRes := <-ch if d.expectedInitial != nil { requireTargetGroups(t, d.expectedInitial, initialRes) } if d.afterStart != nil && d.expectedRes != nil { d.afterStart() res := <-ch requireTargetGroups(t, d.expectedRes, res) } } func requireTargetGroups(t *testing.T, expected, res []*targetgroup.Group) { b1, err := json.Marshal(expected) if err != nil { panic(err) } b2, err := json.Marshal(res) if err != nil { panic(err) } require.JSONEq(t, string(b1), string(b2)) } func nodeStoreKeyFunc(obj interface{}) (string, error) { return obj.(*v1.Node).ObjectMeta.Name, nil } func newFakeNodeInformer() *fakeInformer { return newFakeInformer(nodeStoreKeyFunc) } func makeTestNodeDiscovery() (*Node, *fakeInformer) { i := newFakeNodeInformer() return NewNode(nil, i), i } func makeNode(name, address string, labels map[string]string, annotations map[string]string) *v1.Node { return &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, Annotations: annotations, }, Status: v1.NodeStatus{ Addresses: []v1.NodeAddress{ { Type: v1.NodeInternalIP, Address: address, }, }, DaemonEndpoints: v1.NodeDaemonEndpoints{ KubeletEndpoint: v1.DaemonEndpoint{ Port: 10250, }, }, }, } } func makeEnumeratedNode(i int) *v1.Node { return makeNode(fmt.Sprintf("test%d", i), "1.2.3.4", map[string]string{}, map[string]string{}) } func TestNodeDiscoveryInitial(t *testing.T) { n, i := makeTestNodeDiscovery() i.GetStore().Add(makeNode( "test", "1.2.3.4", map[string]string{"testlabel": "testvalue"}, map[string]string{"testannotation": "testannotationvalue"}, )) k8sDiscoveryTest{ discovery: n, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_name": "test", "__meta_kubernetes_node_label_testlabel": "testvalue", "__meta_kubernetes_node_annotation_testannotation": "testannotationvalue", }, Source: "node/test", }, }, }.Run(t) } func TestNodeDiscoveryAdd(t *testing.T) { n, i := makeTestNodeDiscovery() k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Add(makeEnumeratedNode(1)) }() }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test1", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_name": "test1", }, Source: "node/test1", }, }, }.Run(t) } func TestNodeDiscoveryDelete(t *testing.T) { n, i := makeTestNodeDiscovery() i.GetStore().Add(makeEnumeratedNode(0)) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(makeEnumeratedNode(0)) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test0", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_name": "test0", }, Source: "node/test0", }, }, expectedRes: []*targetgroup.Group{ { Source: "node/test0", }, }, }.Run(t) } func TestNodeDiscoveryDeleteUnknownCacheState(t *testing.T) { n, i := makeTestNodeDiscovery() i.GetStore().Add(makeEnumeratedNode(0)) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makeEnumeratedNode(0)}) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test0", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_name": "test0", }, Source: "node/test0", }, }, expectedRes: []*targetgroup.Group{ { Source: "node/test0", }, }, }.Run(t) } func TestNodeDiscoveryUpdate(t *testing.T) { n, i := makeTestNodeDiscovery() i.GetStore().Add(makeEnumeratedNode(0)) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Update( makeNode( "test0", "1.2.3.4", map[string]string{"Unschedulable": "true"}, map[string]string{}, ), ) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test0", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_name": "test0", }, Source: "node/test0", }, }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:10250", "instance": "test0", "__meta_kubernetes_node_address_InternalIP": "1.2.3.4", }, }, Labels: model.LabelSet{ "__meta_kubernetes_node_label_Unschedulable": "true", "__meta_kubernetes_node_name": "test0", }, Source: "node/test0", }, }, }.Run(t) } prometheus-2.1.0+ds/discovery/kubernetes/pod.go000066400000000000000000000146101323116307200215760ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "net" "strconv" "strings" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/model" "k8s.io/client-go/pkg/api" apiv1 "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" ) // Pod discovers new pod targets. type Pod struct { informer cache.SharedInformer store cache.Store logger log.Logger } // NewPod creates a new pod discovery. func NewPod(l log.Logger, pods cache.SharedInformer) *Pod { if l == nil { l = log.NewNopLogger() } return &Pod{ informer: pods, store: pods.GetStore(), logger: l, } } // Run implements the Discoverer interface. func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Send full initial set of pod targets. var initial []*targetgroup.Group for _, o := range p.store.List() { tg := p.buildPod(o.(*apiv1.Pod)) initial = append(initial, tg) level.Debug(p.logger).Log("msg", "initial pod", "tg", fmt.Sprintf("%#v", tg)) } select { case <-ctx.Done(): return case ch <- initial: } // Send target groups for pod updates. send := func(tg *targetgroup.Group) { level.Debug(p.logger).Log("msg", "pod update", "tg", fmt.Sprintf("%#v", tg)) select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } p.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { eventCount.WithLabelValues("pod", "add").Inc() pod, err := convertToPod(o) if err != nil { level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err) return } send(p.buildPod(pod)) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("pod", "delete").Inc() pod, err := convertToPod(o) if err != nil { level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err) return } send(&targetgroup.Group{Source: podSource(pod)}) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("pod", "update").Inc() pod, err := convertToPod(o) if err != nil { level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err) return } send(p.buildPod(pod)) }, }) // Block until the target provider is explicitly canceled. <-ctx.Done() } func convertToPod(o interface{}) (*apiv1.Pod, error) { pod, ok := o.(*apiv1.Pod) if ok { return pod, nil } deletedState, ok := o.(cache.DeletedFinalStateUnknown) if !ok { return nil, fmt.Errorf("Received unexpected object: %v", o) } pod, ok = deletedState.Obj.(*apiv1.Pod) if !ok { return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Pod object: %v", deletedState.Obj) } return pod, nil } const ( podNameLabel = metaLabelPrefix + "pod_name" podIPLabel = metaLabelPrefix + "pod_ip" podContainerNameLabel = metaLabelPrefix + "pod_container_name" podContainerPortNameLabel = metaLabelPrefix + "pod_container_port_name" podContainerPortNumberLabel = metaLabelPrefix + "pod_container_port_number" podContainerPortProtocolLabel = metaLabelPrefix + "pod_container_port_protocol" podReadyLabel = metaLabelPrefix + "pod_ready" podLabelPrefix = metaLabelPrefix + "pod_label_" podAnnotationPrefix = metaLabelPrefix + "pod_annotation_" podNodeNameLabel = metaLabelPrefix + "pod_node_name" podHostIPLabel = metaLabelPrefix + "pod_host_ip" podUID = metaLabelPrefix + "pod_uid" ) func podLabels(pod *apiv1.Pod) model.LabelSet { ls := model.LabelSet{ podNameLabel: lv(pod.ObjectMeta.Name), podIPLabel: lv(pod.Status.PodIP), podReadyLabel: podReady(pod), podNodeNameLabel: lv(pod.Spec.NodeName), podHostIPLabel: lv(pod.Status.HostIP), podUID: lv(string(pod.ObjectMeta.UID)), } for k, v := range pod.Labels { ln := strutil.SanitizeLabelName(podLabelPrefix + k) ls[model.LabelName(ln)] = lv(v) } for k, v := range pod.Annotations { ln := strutil.SanitizeLabelName(podAnnotationPrefix + k) ls[model.LabelName(ln)] = lv(v) } return ls } func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group { // During startup the pod may not have an IP yet. This does not even allow // for an up metric, so we skip the target. if len(pod.Status.PodIP) == 0 { return nil } tg := &targetgroup.Group{ Source: podSource(pod), } tg.Labels = podLabels(pod) tg.Labels[namespaceLabel] = lv(pod.Namespace) for _, c := range pod.Spec.Containers { // If no ports are defined for the container, create an anonymous // target per container. if len(c.Ports) == 0 { // We don't have a port so we just set the address label to the pod IP. // The user has to add a port manually. tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: lv(pod.Status.PodIP), podContainerNameLabel: lv(c.Name), }) continue } // Otherwise create one target for each container/port combination. for _, port := range c.Ports { ports := strconv.FormatUint(uint64(port.ContainerPort), 10) addr := net.JoinHostPort(pod.Status.PodIP, ports) tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: lv(addr), podContainerNameLabel: lv(c.Name), podContainerPortNumberLabel: lv(ports), podContainerPortNameLabel: lv(port.Name), podContainerPortProtocolLabel: lv(string(port.Protocol)), }) } } return tg } func podSource(pod *apiv1.Pod) string { return "pod/" + pod.Namespace + "/" + pod.Name } func podReady(pod *apiv1.Pod) model.LabelValue { for _, cond := range pod.Status.Conditions { if cond.Type == apiv1.PodReady { return lv(strings.ToLower(string(cond.Status))) } } return lv(strings.ToLower(string(api.ConditionUnknown))) } prometheus-2.1.0+ds/discovery/kubernetes/pod_test.go000066400000000000000000000243211323116307200226350ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "testing" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) func podStoreKeyFunc(obj interface{}) (string, error) { return obj.(*v1.Pod).ObjectMeta.Name, nil } func newFakePodInformer() *fakeInformer { return newFakeInformer(podStoreKeyFunc) } func makeTestPodDiscovery() (*Pod, *fakeInformer) { i := newFakePodInformer() return NewPod(nil, i), i } func makeMultiPortPod() *v1.Pod { return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "testpod", Namespace: "default", Labels: map[string]string{"testlabel": "testvalue"}, Annotations: map[string]string{"testannotation": "testannotationvalue"}, UID: types.UID("abc123"), }, Spec: v1.PodSpec{ NodeName: "testnode", Containers: []v1.Container{ { Name: "testcontainer0", Ports: []v1.ContainerPort{ { Name: "testport0", Protocol: v1.ProtocolTCP, ContainerPort: int32(9000), }, { Name: "testport1", Protocol: v1.ProtocolUDP, ContainerPort: int32(9001), }, }, }, { Name: "testcontainer1", }, }, }, Status: v1.PodStatus{ PodIP: "1.2.3.4", HostIP: "2.3.4.5", Conditions: []v1.PodCondition{ { Type: v1.PodReady, Status: v1.ConditionTrue, }, }, }, } } func makePod() *v1.Pod { return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "testpod", Namespace: "default", UID: types.UID("abc123"), }, Spec: v1.PodSpec{ NodeName: "testnode", Containers: []v1.Container{ { Name: "testcontainer", Ports: []v1.ContainerPort{ { Name: "testport", Protocol: v1.ProtocolTCP, ContainerPort: int32(9000), }, }, }, }, }, Status: v1.PodStatus{ PodIP: "1.2.3.4", HostIP: "2.3.4.5", Conditions: []v1.PodCondition{ { Type: v1.PodReady, Status: v1.ConditionTrue, }, }, }, } } func TestPodDiscoveryInitial(t *testing.T) { n, i := makeTestPodDiscovery() i.GetStore().Add(makeMultiPortPod()) k8sDiscoveryTest{ discovery: n, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer0", "__meta_kubernetes_pod_container_port_name": "testport0", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, { "__address__": "1.2.3.4:9001", "__meta_kubernetes_pod_container_name": "testcontainer0", "__meta_kubernetes_pod_container_port_name": "testport1", "__meta_kubernetes_pod_container_port_number": "9001", "__meta_kubernetes_pod_container_port_protocol": "UDP", }, { "__address__": "1.2.3.4", "__meta_kubernetes_pod_container_name": "testcontainer1", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_label_testlabel": "testvalue", "__meta_kubernetes_pod_annotation_testannotation": "testannotationvalue", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "true", "__meta_kubernetes_pod_uid": "abc123", }, Source: "pod/default/testpod", }, }, }.Run(t) } func TestPodDiscoveryAdd(t *testing.T) { n, i := makeTestPodDiscovery() k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Add(makePod()) }() }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer", "__meta_kubernetes_pod_container_port_name": "testport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "true", "__meta_kubernetes_pod_uid": "abc123", }, Source: "pod/default/testpod", }, }, }.Run(t) } func TestPodDiscoveryDelete(t *testing.T) { n, i := makeTestPodDiscovery() i.GetStore().Add(makePod()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(makePod()) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer", "__meta_kubernetes_pod_container_port_name": "testport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "true", "__meta_kubernetes_pod_uid": "abc123", }, Source: "pod/default/testpod", }, }, expectedRes: []*targetgroup.Group{ { Source: "pod/default/testpod", }, }, }.Run(t) } func TestPodDiscoveryDeleteUnknownCacheState(t *testing.T) { n, i := makeTestPodDiscovery() i.GetStore().Add(makePod()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makePod()}) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer", "__meta_kubernetes_pod_container_port_name": "testport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "true", "__meta_kubernetes_pod_uid": "abc123", }, Source: "pod/default/testpod", }, }, expectedRes: []*targetgroup.Group{ { Source: "pod/default/testpod", }, }, }.Run(t) } func TestPodDiscoveryUpdate(t *testing.T) { n, i := makeTestPodDiscovery() i.GetStore().Add(&v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "testpod", Namespace: "default", UID: "xyz321", }, Spec: v1.PodSpec{ NodeName: "testnode", Containers: []v1.Container{ { Name: "testcontainer", Ports: []v1.ContainerPort{ { Name: "testport", Protocol: v1.ProtocolTCP, ContainerPort: int32(9000), }, }, }, }, }, Status: v1.PodStatus{ PodIP: "1.2.3.4", HostIP: "2.3.4.5", }, }) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Update(makePod()) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer", "__meta_kubernetes_pod_container_port_name": "testport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "unknown", "__meta_kubernetes_pod_uid": "xyz321", }, Source: "pod/default/testpod", }, }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__address__": "1.2.3.4:9000", "__meta_kubernetes_pod_container_name": "testcontainer", "__meta_kubernetes_pod_container_port_name": "testport", "__meta_kubernetes_pod_container_port_number": "9000", "__meta_kubernetes_pod_container_port_protocol": "TCP", }, }, Labels: model.LabelSet{ "__meta_kubernetes_pod_name": "testpod", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_pod_node_name": "testnode", "__meta_kubernetes_pod_ip": "1.2.3.4", "__meta_kubernetes_pod_host_ip": "2.3.4.5", "__meta_kubernetes_pod_ready": "true", "__meta_kubernetes_pod_uid": "abc123", }, Source: "pod/default/testpod", }, }, }.Run(t) } prometheus-2.1.0+ds/discovery/kubernetes/service.go000066400000000000000000000112411323116307200224510ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "fmt" "net" "strconv" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/model" apiv1 "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" ) // Service implements discovery of Kubernetes services. type Service struct { logger log.Logger informer cache.SharedInformer store cache.Store } // NewService returns a new service discovery. func NewService(l log.Logger, inf cache.SharedInformer) *Service { if l == nil { l = log.NewNopLogger() } return &Service{logger: l, informer: inf, store: inf.GetStore()} } // Run implements the Discoverer interface. func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Send full initial set of pod targets. var initial []*targetgroup.Group for _, o := range s.store.List() { tg := s.buildService(o.(*apiv1.Service)) initial = append(initial, tg) } select { case <-ctx.Done(): return case ch <- initial: } // Send target groups for service updates. send := func(tg *targetgroup.Group) { select { case <-ctx.Done(): case ch <- []*targetgroup.Group{tg}: } } s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { eventCount.WithLabelValues("service", "add").Inc() svc, err := convertToService(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err) return } send(s.buildService(svc)) }, DeleteFunc: func(o interface{}) { eventCount.WithLabelValues("service", "delete").Inc() svc, err := convertToService(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err) return } send(&targetgroup.Group{Source: serviceSource(svc)}) }, UpdateFunc: func(_, o interface{}) { eventCount.WithLabelValues("service", "update").Inc() svc, err := convertToService(o) if err != nil { level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err) return } send(s.buildService(svc)) }, }) // Block until the target provider is explicitly canceled. <-ctx.Done() } func convertToService(o interface{}) (*apiv1.Service, error) { service, ok := o.(*apiv1.Service) if ok { return service, nil } deletedState, ok := o.(cache.DeletedFinalStateUnknown) if !ok { return nil, fmt.Errorf("Received unexpected object: %v", o) } service, ok = deletedState.Obj.(*apiv1.Service) if !ok { return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Service object: %v", deletedState.Obj) } return service, nil } func serviceSource(s *apiv1.Service) string { return "svc/" + s.Namespace + "/" + s.Name } const ( serviceNameLabel = metaLabelPrefix + "service_name" serviceLabelPrefix = metaLabelPrefix + "service_label_" serviceAnnotationPrefix = metaLabelPrefix + "service_annotation_" servicePortNameLabel = metaLabelPrefix + "service_port_name" servicePortProtocolLabel = metaLabelPrefix + "service_port_protocol" ) func serviceLabels(svc *apiv1.Service) model.LabelSet { ls := make(model.LabelSet, len(svc.Labels)+len(svc.Annotations)+2) ls[serviceNameLabel] = lv(svc.Name) ls[namespaceLabel] = lv(svc.Namespace) for k, v := range svc.Labels { ln := strutil.SanitizeLabelName(serviceLabelPrefix + k) ls[model.LabelName(ln)] = lv(v) } for k, v := range svc.Annotations { ln := strutil.SanitizeLabelName(serviceAnnotationPrefix + k) ls[model.LabelName(ln)] = lv(v) } return ls } func (s *Service) buildService(svc *apiv1.Service) *targetgroup.Group { tg := &targetgroup.Group{ Source: serviceSource(svc), } tg.Labels = serviceLabels(svc) for _, port := range svc.Spec.Ports { addr := net.JoinHostPort(svc.Name+"."+svc.Namespace+".svc", strconv.FormatInt(int64(port.Port), 10)) tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: lv(addr), servicePortNameLabel: lv(port.Name), servicePortProtocolLabel: lv(string(port.Protocol)), }) } return tg } prometheus-2.1.0+ds/discovery/kubernetes/service_test.go000066400000000000000000000160751323116307200235220ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "fmt" "testing" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/tools/cache" ) func serviceStoreKeyFunc(obj interface{}) (string, error) { return obj.(*v1.Service).ObjectMeta.Name, nil } func newFakeServiceInformer() *fakeInformer { return newFakeInformer(serviceStoreKeyFunc) } func makeTestServiceDiscovery() (*Service, *fakeInformer) { i := newFakeServiceInformer() return NewService(nil, i), i } func makeMultiPortService() *v1.Service { return &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "testservice", Namespace: "default", Labels: map[string]string{"testlabel": "testvalue"}, Annotations: map[string]string{"testannotation": "testannotationvalue"}, }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "testport0", Protocol: v1.ProtocolTCP, Port: int32(30900), }, { Name: "testport1", Protocol: v1.ProtocolUDP, Port: int32(30901), }, }, }, } } func makeSuffixedService(suffix string) *v1.Service { return &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("testservice%s", suffix), Namespace: "default", }, Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { Name: "testport", Protocol: v1.ProtocolTCP, Port: int32(30900), }, }, }, } } func makeService() *v1.Service { return makeSuffixedService("") } func TestServiceDiscoveryInitial(t *testing.T) { n, i := makeTestServiceDiscovery() i.GetStore().Add(makeMultiPortService()) k8sDiscoveryTest{ discovery: n, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport0", }, { "__meta_kubernetes_service_port_protocol": "UDP", "__address__": "testservice.default.svc:30901", "__meta_kubernetes_service_port_name": "testport1", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_service_label_testlabel": "testvalue", "__meta_kubernetes_service_annotation_testannotation": "testannotationvalue", }, Source: "svc/default/testservice", }, }, }.Run(t) } func TestServiceDiscoveryAdd(t *testing.T) { n, i := makeTestServiceDiscovery() k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Add(makeService()) }() }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", }, Source: "svc/default/testservice", }, }, }.Run(t) } func TestServiceDiscoveryDelete(t *testing.T) { n, i := makeTestServiceDiscovery() i.GetStore().Add(makeService()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(makeService()) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", }, Source: "svc/default/testservice", }, }, expectedRes: []*targetgroup.Group{ { Source: "svc/default/testservice", }, }, }.Run(t) } func TestServiceDiscoveryDeleteUnknownCacheState(t *testing.T) { n, i := makeTestServiceDiscovery() i.GetStore().Add(makeService()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makeService()}) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", }, Source: "svc/default/testservice", }, }, expectedRes: []*targetgroup.Group{ { Source: "svc/default/testservice", }, }, }.Run(t) } func TestServiceDiscoveryUpdate(t *testing.T) { n, i := makeTestServiceDiscovery() i.GetStore().Add(makeService()) k8sDiscoveryTest{ discovery: n, afterStart: func() { go func() { i.Update(makeMultiPortService()) }() }, expectedInitial: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", }, Source: "svc/default/testservice", }, }, expectedRes: []*targetgroup.Group{ { Targets: []model.LabelSet{ { "__meta_kubernetes_service_port_protocol": "TCP", "__address__": "testservice.default.svc:30900", "__meta_kubernetes_service_port_name": "testport0", }, { "__meta_kubernetes_service_port_protocol": "UDP", "__address__": "testservice.default.svc:30901", "__meta_kubernetes_service_port_name": "testport1", }, }, Labels: model.LabelSet{ "__meta_kubernetes_service_name": "testservice", "__meta_kubernetes_namespace": "default", "__meta_kubernetes_service_label_testlabel": "testvalue", "__meta_kubernetes_service_annotation_testannotation": "testannotationvalue", }, Source: "svc/default/testservice", }, }, }.Run(t) } prometheus-2.1.0+ds/discovery/manager.go000066400000000000000000000220351323116307200202570ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package discovery import ( "context" "fmt" "sync" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" sd_config "github.com/prometheus/prometheus/discovery/config" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/discovery/azure" "github.com/prometheus/prometheus/discovery/consul" "github.com/prometheus/prometheus/discovery/dns" "github.com/prometheus/prometheus/discovery/ec2" "github.com/prometheus/prometheus/discovery/file" "github.com/prometheus/prometheus/discovery/gce" "github.com/prometheus/prometheus/discovery/kubernetes" "github.com/prometheus/prometheus/discovery/marathon" "github.com/prometheus/prometheus/discovery/openstack" "github.com/prometheus/prometheus/discovery/triton" "github.com/prometheus/prometheus/discovery/zookeeper" ) // Discoverer provides information about target groups. It maintains a set // of sources from which TargetGroups can originate. Whenever a discovery provider // detects a potential change, it sends the TargetGroup through its channel. // // Discoverer does not know if an actual change happened. // It does guarantee that it sends the new TargetGroup whenever a change happens. // // Discoverers should initially send a full set of all discoverable TargetGroups. type Discoverer interface { // Run hands a channel to the discovery provider(consul,dns etc) through which it can send // updated target groups. // Must returns if the context gets canceled. It should not close the update // channel on returning. Run(ctx context.Context, up chan<- []*targetgroup.Group) } type poolKey struct { setName string provider string } // NewManager is the Discovery Manager constructor func NewManager(logger log.Logger) *Manager { return &Manager{ logger: logger, syncCh: make(chan map[string][]*targetgroup.Group), targets: make(map[poolKey]map[string]*targetgroup.Group), discoverCancel: []context.CancelFunc{}, ctx: context.Background(), } } // Manager maintains a set of discovery providers and sends each update to a map channel. // Targets are grouped by the target set name. type Manager struct { logger log.Logger mtx sync.RWMutex ctx context.Context discoverCancel []context.CancelFunc // Some Discoverers(eg. k8s) send only the updates for a given target group // so we use map[tg.Source]*targetgroup.Group to know which group to update. targets map[poolKey]map[string]*targetgroup.Group // The sync channels sends the updates in map[targetSetName] where targetSetName is the job value from the scrape config. syncCh chan map[string][]*targetgroup.Group } // Run starts the background processing func (m *Manager) Run(ctx context.Context) error { m.ctx = ctx for { select { case <-ctx.Done(): m.cancelDiscoverers() return ctx.Err() } } } // SyncCh returns a read only channel used by all Discoverers to send target updates. func (m *Manager) SyncCh() <-chan map[string][]*targetgroup.Group { return m.syncCh } // ApplyConfig removes all running discovery providers and starts new ones using the provided config. func (m *Manager) ApplyConfig(cfg map[string]sd_config.ServiceDiscoveryConfig) error { m.mtx.Lock() defer m.mtx.Unlock() m.cancelDiscoverers() for name, scfg := range cfg { for provName, prov := range m.providersFromConfig(scfg) { m.startProvider(m.ctx, poolKey{setName: name, provider: provName}, prov) } } return nil } func (m *Manager) startProvider(ctx context.Context, poolKey poolKey, worker Discoverer) { ctx, cancel := context.WithCancel(ctx) updates := make(chan []*targetgroup.Group) m.discoverCancel = append(m.discoverCancel, cancel) go worker.Run(ctx, updates) go m.runProvider(ctx, poolKey, updates) } func (m *Manager) runProvider(ctx context.Context, poolKey poolKey, updates chan []*targetgroup.Group) { for { select { case <-ctx.Done(): return case tgs, ok := <-updates: // Handle the case that a target provider exits and closes the channel // before the context is done. if !ok { return } m.updateGroup(poolKey, tgs) m.syncCh <- m.allGroups() } } } func (m *Manager) cancelDiscoverers() { for _, c := range m.discoverCancel { c() } m.targets = make(map[poolKey]map[string]*targetgroup.Group) m.discoverCancel = nil } func (m *Manager) updateGroup(poolKey poolKey, tgs []*targetgroup.Group) { m.mtx.Lock() defer m.mtx.Unlock() for _, tg := range tgs { if tg != nil { // Some Discoverers send nil target group so need to check for it to avoid panics. if _, ok := m.targets[poolKey]; !ok { m.targets[poolKey] = make(map[string]*targetgroup.Group) } m.targets[poolKey][tg.Source] = tg } } } func (m *Manager) allGroups() map[string][]*targetgroup.Group { m.mtx.Lock() defer m.mtx.Unlock() tSets := map[string][]*targetgroup.Group{} for pkey, tsets := range m.targets { for _, tg := range tsets { // Even if the target group 'tg' is empty we still need to send it to the 'Scrape manager' // to signal that it needs to stop all scrape loops for this target set. tSets[pkey.setName] = append(tSets[pkey.setName], tg) } } return tSets } func (m *Manager) providersFromConfig(cfg sd_config.ServiceDiscoveryConfig) map[string]Discoverer { providers := map[string]Discoverer{} app := func(mech string, i int, tp Discoverer) { providers[fmt.Sprintf("%s/%d", mech, i)] = tp } for i, c := range cfg.DNSSDConfigs { app("dns", i, dns.NewDiscovery(*c, log.With(m.logger, "discovery", "dns"))) } for i, c := range cfg.FileSDConfigs { app("file", i, file.NewDiscovery(c, log.With(m.logger, "discovery", "file"))) } for i, c := range cfg.ConsulSDConfigs { k, err := consul.NewDiscovery(c, log.With(m.logger, "discovery", "consul")) if err != nil { level.Error(m.logger).Log("msg", "Cannot create Consul discovery", "err", err) continue } app("consul", i, k) } for i, c := range cfg.MarathonSDConfigs { t, err := marathon.NewDiscovery(*c, log.With(m.logger, "discovery", "marathon")) if err != nil { level.Error(m.logger).Log("msg", "Cannot create Marathon discovery", "err", err) continue } app("marathon", i, t) } for i, c := range cfg.KubernetesSDConfigs { k, err := kubernetes.New(log.With(m.logger, "discovery", "k8s"), c) if err != nil { level.Error(m.logger).Log("msg", "Cannot create Kubernetes discovery", "err", err) continue } app("kubernetes", i, k) } for i, c := range cfg.ServersetSDConfigs { app("serverset", i, zookeeper.NewServersetDiscovery(c, log.With(m.logger, "discovery", "zookeeper"))) } for i, c := range cfg.NerveSDConfigs { app("nerve", i, zookeeper.NewNerveDiscovery(c, log.With(m.logger, "discovery", "nerve"))) } for i, c := range cfg.EC2SDConfigs { app("ec2", i, ec2.NewDiscovery(c, log.With(m.logger, "discovery", "ec2"))) } for i, c := range cfg.OpenstackSDConfigs { openstackd, err := openstack.NewDiscovery(c, log.With(m.logger, "discovery", "openstack")) if err != nil { level.Error(m.logger).Log("msg", "Cannot initialize OpenStack discovery", "err", err) continue } app("openstack", i, openstackd) } for i, c := range cfg.GCESDConfigs { gced, err := gce.NewDiscovery(*c, log.With(m.logger, "discovery", "gce")) if err != nil { level.Error(m.logger).Log("msg", "Cannot initialize GCE discovery", "err", err) continue } app("gce", i, gced) } for i, c := range cfg.AzureSDConfigs { app("azure", i, azure.NewDiscovery(c, log.With(m.logger, "discovery", "azure"))) } for i, c := range cfg.TritonSDConfigs { t, err := triton.New(log.With(m.logger, "discovery", "trition"), c) if err != nil { level.Error(m.logger).Log("msg", "Cannot create Triton discovery", "err", err) continue } app("triton", i, t) } if len(cfg.StaticConfigs) > 0 { app("static", 0, NewStaticProvider(cfg.StaticConfigs)) } return providers } // StaticProvider holds a list of target groups that never change. type StaticProvider struct { TargetGroups []*targetgroup.Group } // NewStaticProvider returns a StaticProvider configured with the given // target groups. func NewStaticProvider(groups []*targetgroup.Group) *StaticProvider { for i, tg := range groups { tg.Source = fmt.Sprintf("%d", i) } return &StaticProvider{groups} } // Run implements the Worker interface. func (sd *StaticProvider) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // We still have to consider that the consumer exits right away in which case // the context will be canceled. select { case ch <- sd.TargetGroups: case <-ctx.Done(): } close(ch) } prometheus-2.1.0+ds/discovery/manager_test.go000066400000000000000000000461761323116307200213320ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package discovery import ( "context" "fmt" "reflect" "sort" "strconv" "testing" "time" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/config" sd_config "github.com/prometheus/prometheus/discovery/config" "github.com/prometheus/prometheus/discovery/targetgroup" "gopkg.in/yaml.v2" ) // TestDiscoveryManagerSyncCalls checks that the target updates are received in the expected order. func TestDiscoveryManagerSyncCalls(t *testing.T) { // The order by which the updates are send is detirmened by the interval passed to the mock discovery adapter // Final targets array is ordered alphabetically by the name of the discoverer. // For example discoverer "A" with targets "t2,t3" and discoverer "B" with targets "t1,t2" will result in "t2,t3,t1,t2" after the merge. testCases := []struct { title string updates map[string][]update expectedTargets [][]*targetgroup.Group }{ { title: "Single TP no updates", updates: map[string][]update{ "tp1": {}, }, expectedTargets: nil, }, { title: "Multips TPs no updates", updates: map[string][]update{ "tp1": {}, "tp2": {}, "tp3": {}, }, expectedTargets: nil, }, { title: "Single TP empty initials", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{}, interval: 5, }, }, }, expectedTargets: [][]*targetgroup.Group{ {}, }, }, { title: "Multiple TPs empty initials", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{}, interval: 5, }, }, "tp2": { { targetGroups: []targetgroup.Group{}, interval: 200, }, }, "tp3": { { targetGroups: []targetgroup.Group{}, interval: 100, }, }, }, expectedTargets: [][]*targetgroup.Group{ {}, {}, {}, }, }, { title: "Single TP initials only", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }}, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, }, }, { title: "Multiple TPs initials only", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, }, }, "tp2": { { targetGroups: []targetgroup.Group{ { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, }, interval: 10, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, }, }, }, { title: "Single TP initials followed by empty updates", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, interval: 0, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{}, }, { Source: "tp1_group2", Targets: []model.LabelSet{}, }, }, interval: 10, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{}, }, { Source: "tp1_group2", Targets: []model.LabelSet{}, }, }, }, }, { title: "Single TP initials and new groups", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, interval: 0, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, { Source: "tp1_group3", Targets: []model.LabelSet{{"__instance__": "1"}}, }, }, interval: 10, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, { Source: "tp1_group3", Targets: []model.LabelSet{{"__instance__": "1"}}, }, }, }, }, { title: "Multiple TPs initials and new groups", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, interval: 10, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group3", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group4", Targets: []model.LabelSet{{"__instance__": "4"}}, }, }, interval: 500, }, }, "tp2": { { targetGroups: []targetgroup.Group{ { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, }, interval: 100, }, { targetGroups: []targetgroup.Group{ { Source: "tp2_group3", Targets: []model.LabelSet{{"__instance__": "7"}}, }, { Source: "tp2_group4", Targets: []model.LabelSet{{"__instance__": "8"}}, }, }, interval: 10, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, { Source: "tp2_group3", Targets: []model.LabelSet{{"__instance__": "7"}}, }, { Source: "tp2_group4", Targets: []model.LabelSet{{"__instance__": "8"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, { Source: "tp1_group3", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group4", Targets: []model.LabelSet{{"__instance__": "4"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, { Source: "tp2_group3", Targets: []model.LabelSet{{"__instance__": "7"}}, }, { Source: "tp2_group4", Targets: []model.LabelSet{{"__instance__": "8"}}, }, }, }, }, { title: "One TP initials arrive after other TP updates.", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, interval: 10, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, }, interval: 150, }, }, "tp2": { { targetGroups: []targetgroup.Group{ { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, }, interval: 200, }, { targetGroups: []targetgroup.Group{ { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "7"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "8"}}, }, }, interval: 100, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "5"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "6"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, { Source: "tp2_group1", Targets: []model.LabelSet{{"__instance__": "7"}}, }, { Source: "tp2_group2", Targets: []model.LabelSet{{"__instance__": "8"}}, }, }, }, }, { title: "Single TP empty update in between", updates: map[string][]update{ "tp1": { { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, interval: 30, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{}, }, { Source: "tp1_group2", Targets: []model.LabelSet{}, }, }, interval: 10, }, { targetGroups: []targetgroup.Group{ { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, }, interval: 300, }, }, }, expectedTargets: [][]*targetgroup.Group{ { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "1"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "2"}}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{}, }, { Source: "tp1_group2", Targets: []model.LabelSet{}, }, }, { { Source: "tp1_group1", Targets: []model.LabelSet{{"__instance__": "3"}}, }, { Source: "tp1_group2", Targets: []model.LabelSet{{"__instance__": "4"}}, }, }, }, }, } for testIndex, testCase := range testCases { ctx, cancel := context.WithCancel(context.Background()) defer cancel() discoveryManager := NewManager(nil) go discoveryManager.Run(ctx) var totalUpdatesCount int for tpName, update := range testCase.updates { provider := newMockDiscoveryProvider(update) discoveryManager.startProvider(ctx, poolKey{setName: strconv.Itoa(testIndex), provider: tpName}, provider) if len(update) > 0 { totalUpdatesCount = totalUpdatesCount + len(update) } } Loop: for x := 0; x < totalUpdatesCount; x++ { select { case <-time.After(10 * time.Second): t.Errorf("%v. %q: no update arrived within the timeout limit", x, testCase.title) break Loop case tsetMap := <-discoveryManager.SyncCh(): for _, received := range tsetMap { // Need to sort by the Groups source as the Discovery manager doesn't guarantee the order. sort.Sort(byGroupSource(received)) if !reflect.DeepEqual(received, testCase.expectedTargets[x]) { var receivedFormated string for _, receivedTargets := range received { receivedFormated = receivedFormated + receivedTargets.Source + ":" + fmt.Sprint(receivedTargets.Targets) } var expectedFormated string for _, expectedTargets := range testCase.expectedTargets[x] { expectedFormated = expectedFormated + expectedTargets.Source + ":" + fmt.Sprint(expectedTargets.Targets) } t.Errorf("%v. %v: \ntargets mismatch \nreceived: %v \nexpected: %v", x, testCase.title, receivedFormated, expectedFormated) } } } } } } func TestTargetSetRecreatesTargetGroupsEveryRun(t *testing.T) { verifyPresence := func(tSets map[poolKey]map[string]*targetgroup.Group, poolKey poolKey, label string, present bool) { if _, ok := tSets[poolKey]; !ok { t.Fatalf("'%s' should be present in Pool keys: %v", poolKey, tSets) return } match := false var mergedTargets string for _, targetGroup := range tSets[poolKey] { for _, l := range targetGroup.Targets { mergedTargets = mergedTargets + " " + l.String() if l.String() == label { match = true } } } if match != present { msg := "" if !present { msg = "not" } t.Fatalf("'%s' should %s be present in Targets labels: %v", label, msg, mergedTargets) } } cfg := &config.Config{} sOne := ` scrape_configs: - job_name: 'prometheus' static_configs: - targets: ["foo:9090"] - targets: ["bar:9090"] ` if err := yaml.Unmarshal([]byte(sOne), cfg); err != nil { t.Fatalf("Unable to load YAML config sOne: %s", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() discoveryManager := NewManager(nil) go discoveryManager.Run(ctx) c := make(map[string]sd_config.ServiceDiscoveryConfig) for _, v := range cfg.ScrapeConfigs { c[v.JobName] = v.ServiceDiscoveryConfig } discoveryManager.ApplyConfig(c) _ = <-discoveryManager.SyncCh() verifyPresence(discoveryManager.targets, poolKey{setName: "prometheus", provider: "static/0"}, "{__address__=\"foo:9090\"}", true) verifyPresence(discoveryManager.targets, poolKey{setName: "prometheus", provider: "static/0"}, "{__address__=\"bar:9090\"}", true) sTwo := ` scrape_configs: - job_name: 'prometheus' static_configs: - targets: ["foo:9090"] ` if err := yaml.Unmarshal([]byte(sTwo), cfg); err != nil { t.Fatalf("Unable to load YAML config sOne: %s", err) } c = make(map[string]sd_config.ServiceDiscoveryConfig) for _, v := range cfg.ScrapeConfigs { c[v.JobName] = v.ServiceDiscoveryConfig } discoveryManager.ApplyConfig(c) _ = <-discoveryManager.SyncCh() verifyPresence(discoveryManager.targets, poolKey{setName: "prometheus", provider: "static/0"}, "{__address__=\"foo:9090\"}", true) verifyPresence(discoveryManager.targets, poolKey{setName: "prometheus", provider: "static/0"}, "{__address__=\"bar:9090\"}", false) } type update struct { targetGroups []targetgroup.Group interval time.Duration } type mockdiscoveryProvider struct { updates []update up chan<- []*targetgroup.Group } func newMockDiscoveryProvider(updates []update) mockdiscoveryProvider { tp := mockdiscoveryProvider{ updates: updates, } return tp } func (tp mockdiscoveryProvider) Run(ctx context.Context, up chan<- []*targetgroup.Group) { tp.up = up tp.sendUpdates() } func (tp mockdiscoveryProvider) sendUpdates() { for _, update := range tp.updates { time.Sleep(update.interval * time.Millisecond) tgs := make([]*targetgroup.Group, len(update.targetGroups)) for i := range update.targetGroups { tgs[i] = &update.targetGroups[i] } tp.up <- tgs } } // byGroupSource implements sort.Interface so we can sort by the Source field. type byGroupSource []*targetgroup.Group func (a byGroupSource) Len() int { return len(a) } func (a byGroupSource) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byGroupSource) Less(i, j int) bool { return a[i].Source < a[j].Source } prometheus-2.1.0+ds/discovery/marathon/000077500000000000000000000000001323116307200201255ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/marathon/marathon.go000066400000000000000000000302311323116307200222640ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package marathon import ( "context" "encoding/json" "fmt" "io/ioutil" "math/rand" "net" "net/http" "strconv" "strings" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" conntrack "github.com/mwitkow/go-conntrack" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/httputil" "github.com/prometheus/prometheus/util/strutil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( // metaLabelPrefix is the meta prefix used for all meta labels in this discovery. metaLabelPrefix = model.MetaLabelPrefix + "marathon_" // appLabelPrefix is the prefix for the application labels. appLabelPrefix = metaLabelPrefix + "app_label_" // appLabel is used for the name of the app in Marathon. appLabel model.LabelName = metaLabelPrefix + "app" // imageLabel is the label that is used for the docker image running the service. imageLabel model.LabelName = metaLabelPrefix + "image" // portIndexLabel is the integer port index when multiple ports are defined; // e.g. PORT1 would have a value of '1' portIndexLabel model.LabelName = metaLabelPrefix + "port_index" // taskLabel contains the mesos task name of the app instance. taskLabel model.LabelName = metaLabelPrefix + "task" // portMappingLabelPrefix is the prefix for the application portMappings labels. portMappingLabelPrefix = metaLabelPrefix + "port_mapping_label_" // portDefinitionLabelPrefix is the prefix for the application portDefinitions labels. portDefinitionLabelPrefix = metaLabelPrefix + "port_definition_label_" // Constants for instrumentation. namespace = "prometheus" ) var ( refreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: namespace, Name: "sd_marathon_refresh_failures_total", Help: "The number of Marathon-SD refresh failures.", }) refreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Namespace: namespace, Name: "sd_marathon_refresh_duration_seconds", Help: "The duration of a Marathon-SD refresh in seconds.", }) // DefaultSDConfig is the default Marathon SD configuration. DefaultSDConfig = SDConfig{ Timeout: model.Duration(30 * time.Second), RefreshInterval: model.Duration(30 * time.Second), } ) // SDConfig is the configuration for services running on Marathon. type SDConfig struct { Servers []string `yaml:"servers,omitempty"` Timeout model.Duration `yaml:"timeout,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"` BearerToken config_util.Secret `yaml:"bearer_token,omitempty"` BearerTokenFile string `yaml:"bearer_token_file,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "marathon_sd_config"); err != nil { return err } if len(c.Servers) == 0 { return fmt.Errorf("Marathon SD config must contain at least one Marathon server") } if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 { return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured") } return nil } func init() { prometheus.MustRegister(refreshFailuresCount) prometheus.MustRegister(refreshDuration) } const appListPath string = "/v2/apps/?embed=apps.tasks" // Discovery provides service discovery based on a Marathon instance. type Discovery struct { client *http.Client servers []string refreshInterval time.Duration lastRefresh map[string]*targetgroup.Group appsClient AppListClient token string logger log.Logger } // NewDiscovery returns a new Marathon Discovery. func NewDiscovery(conf SDConfig, logger log.Logger) (*Discovery, error) { if logger == nil { logger = log.NewNopLogger() } tls, err := httputil.NewTLSConfig(conf.TLSConfig) if err != nil { return nil, err } token := string(conf.BearerToken) if conf.BearerTokenFile != "" { bf, err := ioutil.ReadFile(conf.BearerTokenFile) if err != nil { return nil, err } token = strings.TrimSpace(string(bf)) } client := &http.Client{ Timeout: time.Duration(conf.Timeout), Transport: &http.Transport{ TLSClientConfig: tls, DialContext: conntrack.NewDialContextFunc( conntrack.DialWithTracing(), conntrack.DialWithName("marathon_sd"), ), }, } return &Discovery{ client: client, servers: conf.Servers, refreshInterval: time.Duration(conf.RefreshInterval), appsClient: fetchApps, token: token, logger: logger, }, nil } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { for { select { case <-ctx.Done(): return case <-time.After(d.refreshInterval): err := d.updateServices(ctx, ch) if err != nil { level.Error(d.logger).Log("msg", "Error while updating services", "err", err) } } } } func (d *Discovery) updateServices(ctx context.Context, ch chan<- []*targetgroup.Group) (err error) { t0 := time.Now() defer func() { refreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { refreshFailuresCount.Inc() } }() targetMap, err := d.fetchTargetGroups() if err != nil { return err } all := make([]*targetgroup.Group, 0, len(targetMap)) for _, tg := range targetMap { all = append(all, tg) } select { case <-ctx.Done(): return ctx.Err() case ch <- all: } // Remove services which did disappear. for source := range d.lastRefresh { _, ok := targetMap[source] if !ok { select { case <-ctx.Done(): return ctx.Err() case ch <- []*targetgroup.Group{{Source: source}}: level.Debug(d.logger).Log("msg", "Removing group", "source", source) } } } d.lastRefresh = targetMap return nil } func (d *Discovery) fetchTargetGroups() (map[string]*targetgroup.Group, error) { url := RandomAppsURL(d.servers) apps, err := d.appsClient(d.client, url, d.token) if err != nil { return nil, err } groups := AppsToTargetGroups(apps) return groups, nil } // Task describes one instance of a service running on Marathon. type Task struct { ID string `json:"id"` Host string `json:"host"` Ports []uint32 `json:"ports"` } // PortMappings describes in which port the process are binding inside the docker container. type PortMappings struct { Labels map[string]string `json:"labels"` } // DockerContainer describes a container which uses the docker runtime. type DockerContainer struct { Image string `json:"image"` PortMappings []PortMappings `json:"portMappings"` } // Container describes the runtime an app in running in. type Container struct { Docker DockerContainer `json:"docker"` PortMappings []PortMappings `json:"portMappings"` } // PortDefinitions describes which load balancer port you should access to access the service. type PortDefinitions struct { Labels map[string]string `json:"labels"` } // App describes a service running on Marathon. type App struct { ID string `json:"id"` Tasks []Task `json:"tasks"` RunningTasks int `json:"tasksRunning"` Labels map[string]string `json:"labels"` Container Container `json:"container"` PortDefinitions []PortDefinitions `json:"portDefinitions"` } // AppList is a list of Marathon apps. type AppList struct { Apps []App `json:"apps"` } // AppListClient defines a function that can be used to get an application list from marathon. type AppListClient func(client *http.Client, url, token string) (*AppList, error) // fetchApps requests a list of applications from a marathon server. func fetchApps(client *http.Client, url, token string) (*AppList, error) { request, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } // According to https://dcos.io/docs/1.8/administration/id-and-access-mgt/managing-authentication // DC/OS wants with "token=" a different Authorization header than implemented in httputil/client.go // so we set this implicitly here if token != "" { request.Header.Set("Authorization", "token="+token) } resp, err := client.Do(request) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return parseAppJSON(body) } func parseAppJSON(body []byte) (*AppList, error) { apps := &AppList{} err := json.Unmarshal(body, apps) if err != nil { return nil, err } return apps, nil } // RandomAppsURL randomly selects a server from an array and creates // an URL pointing to the app list. func RandomAppsURL(servers []string) string { // TODO: If possible update server list from Marathon at some point. server := servers[rand.Intn(len(servers))] return fmt.Sprintf("%s%s", server, appListPath) } // AppsToTargetGroups takes an array of Marathon apps and converts them into target groups. func AppsToTargetGroups(apps *AppList) map[string]*targetgroup.Group { tgroups := map[string]*targetgroup.Group{} for _, a := range apps.Apps { group := createTargetGroup(&a) tgroups[group.Source] = group } return tgroups } func createTargetGroup(app *App) *targetgroup.Group { var ( targets = targetsForApp(app) appName = model.LabelValue(app.ID) image = model.LabelValue(app.Container.Docker.Image) ) tg := &targetgroup.Group{ Targets: targets, Labels: model.LabelSet{ appLabel: appName, imageLabel: image, }, Source: app.ID, } for ln, lv := range app.Labels { ln = appLabelPrefix + strutil.SanitizeLabelName(ln) tg.Labels[model.LabelName(ln)] = model.LabelValue(lv) } return tg } func targetsForApp(app *App) []model.LabelSet { targets := make([]model.LabelSet, 0, len(app.Tasks)) for _, t := range app.Tasks { if len(t.Ports) == 0 { continue } for i := 0; i < len(t.Ports); i++ { targetAddress := targetForTask(&t, i) target := model.LabelSet{ model.AddressLabel: model.LabelValue(targetAddress), taskLabel: model.LabelValue(t.ID), portIndexLabel: model.LabelValue(strconv.Itoa(i)), } if i < len(app.PortDefinitions) { for ln, lv := range app.PortDefinitions[i].Labels { ln = portDefinitionLabelPrefix + strutil.SanitizeLabelName(ln) target[model.LabelName(ln)] = model.LabelValue(lv) } } // Prior to Marathon 1.5 the port mappings could be found at the path // "container.docker.portMappings". When support for Marathon 1.4 // is dropped then this section of code can be removed. if i < len(app.Container.Docker.PortMappings) { for ln, lv := range app.Container.Docker.PortMappings[i].Labels { ln = portMappingLabelPrefix + strutil.SanitizeLabelName(ln) target[model.LabelName(ln)] = model.LabelValue(lv) } } // In Marathon 1.5.x the container.docker.portMappings object was moved // to container.portMappings. if i < len(app.Container.PortMappings) { for ln, lv := range app.Container.PortMappings[i].Labels { ln = portMappingLabelPrefix + strutil.SanitizeLabelName(ln) target[model.LabelName(ln)] = model.LabelValue(lv) } } targets = append(targets, target) } } return targets } func targetForTask(task *Task, index int) string { return net.JoinHostPort(task.Host, fmt.Sprintf("%d", task.Ports[index])) } prometheus-2.1.0+ds/discovery/marathon/marathon_test.go000066400000000000000000000366571323116307200233450ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package marathon import ( "context" "errors" "net/http" "testing" "time" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" ) var ( marathonValidLabel = map[string]string{"prometheus": "yes"} testServers = []string{"http://localhost:8080"} conf = SDConfig{Servers: testServers} ) func testUpdateServices(client AppListClient, ch chan []*targetgroup.Group) error { md, err := NewDiscovery(conf, nil) if err != nil { return err } md.appsClient = client return md.updateServices(context.Background(), ch) } func TestMarathonSDHandleError(t *testing.T) { var ( errTesting = errors.New("testing failure") ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return nil, errTesting } ) if err := testUpdateServices(client, ch); err != errTesting { t.Fatalf("Expected error: %s", err) } select { case tg := <-ch: t.Fatalf("Got group: %s", tg) default: } } func TestMarathonSDEmptyList(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return &AppList{}, nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tg := <-ch: if len(tg) > 0 { t.Fatalf("Got group: %v", tg) } default: } } func marathonTestAppList(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-1", Host: "mesos-slave1", Ports: []uint32{31000}, } docker = DockerContainer{ Image: "repo/image:tag", PortMappings: []PortMappings{ {Labels: labels}, }, } container = Container{Docker: docker} app = App{ ID: "test-service", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, PortDefinitions: []PortDefinitions{ {Labels: make(map[string]string)}, }, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonSDSendGroup(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppList(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 1 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } tgt := tg.Targets[0] if tgt[model.AddressLabel] != "mesos-slave1:31000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) } default: t.Fatal("Did not get a target group.") } } func TestMarathonSDRemoveApp(t *testing.T) { var ch = make(chan []*targetgroup.Group, 1) md, err := NewDiscovery(conf, nil) if err != nil { t.Fatalf("%s", err) } md.appsClient = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppList(marathonValidLabel, 1), nil } if err := md.updateServices(context.Background(), ch); err != nil { t.Fatalf("Got error on first update: %s", err) } up1 := (<-ch)[0] md.appsClient = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppList(marathonValidLabel, 0), nil } if err := md.updateServices(context.Background(), ch); err != nil { t.Fatalf("Got error on second update: %s", err) } up2 := (<-ch)[0] if up2.Source != up1.Source { t.Fatalf("Source is different: %s", up2) if len(up2.Targets) > 0 { t.Fatalf("Got a non-empty target set: %s", up2.Targets) } } } func TestMarathonSDRunAndStop(t *testing.T) { var ( refreshInterval = model.Duration(time.Millisecond * 10) conf = SDConfig{Servers: testServers, RefreshInterval: refreshInterval} ch = make(chan []*targetgroup.Group) doneCh = make(chan error) ) md, err := NewDiscovery(conf, nil) if err != nil { t.Fatalf("%s", err) } md.appsClient = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppList(marathonValidLabel, 1), nil } ctx, cancel := context.WithCancel(context.Background()) go func() { md.Run(ctx, ch) close(doneCh) }() timeout := time.After(md.refreshInterval * 3) for { select { case <-ch: cancel() case <-doneCh: return case <-timeout: t.Fatalf("Update took too long.") } } } func marathonTestAppListWithMutiplePorts(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-1", Host: "mesos-slave1", Ports: []uint32{31000, 32000}, } docker = DockerContainer{ Image: "repo/image:tag", PortMappings: []PortMappings{ {Labels: labels}, {Labels: make(map[string]string)}, }, } container = Container{Docker: docker} app = App{ ID: "test-service", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, PortDefinitions: []PortDefinitions{ {Labels: make(map[string]string)}, {Labels: labels}, }, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonSDSendGroupWithMutiplePort(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppListWithMutiplePorts(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 2 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } tgt := tg.Targets[0] if tgt[model.AddressLabel] != "mesos-slave1:31000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) } tgt = tg.Targets[1] if tgt[model.AddressLabel] != "mesos-slave1:32000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) } default: t.Fatal("Did not get a target group.") } } func marathonTestZeroTaskPortAppList(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-2", Host: "mesos-slave-2", Ports: []uint32{}, } docker = DockerContainer{Image: "repo/image:tag"} container = Container{Docker: docker} app = App{ ID: "test-service-zero-ports", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonZeroTaskPorts(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestZeroTaskPortAppList(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service-zero-ports" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 0 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } default: t.Fatal("Did not get a target group.") } } func marathonTestAppListWithoutPortMappings(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-1", Host: "mesos-slave1", Ports: []uint32{31000, 32000}, } docker = DockerContainer{ Image: "repo/image:tag", } container = Container{Docker: docker} app = App{ ID: "test-service", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, PortDefinitions: []PortDefinitions{ {Labels: make(map[string]string)}, {Labels: labels}, }, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonSDSendGroupWithoutPortMappings(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppListWithoutPortMappings(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 2 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } tgt := tg.Targets[0] if tgt[model.AddressLabel] != "mesos-slave1:31000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) } tgt = tg.Targets[1] if tgt[model.AddressLabel] != "mesos-slave1:32000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) } default: t.Fatal("Did not get a target group.") } } func marathonTestAppListWithoutPortDefinitions(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-1", Host: "mesos-slave1", Ports: []uint32{31000, 32000}, } docker = DockerContainer{ Image: "repo/image:tag", PortMappings: []PortMappings{ {Labels: labels}, {Labels: make(map[string]string)}, }, } container = Container{Docker: docker} app = App{ ID: "test-service", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonSDSendGroupWithoutPortDefinitions(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppListWithoutPortDefinitions(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 2 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } tgt := tg.Targets[0] if tgt[model.AddressLabel] != "mesos-slave1:31000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) } tgt = tg.Targets[1] if tgt[model.AddressLabel] != "mesos-slave1:32000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) } default: t.Fatal("Did not get a target group.") } } func marathonTestAppListWithContainerPortMappings(labels map[string]string, runningTasks int) *AppList { var ( task = Task{ ID: "test-task-1", Host: "mesos-slave1", Ports: []uint32{31000, 32000}, } docker = DockerContainer{ Image: "repo/image:tag", } container = Container{ Docker: docker, PortMappings: []PortMappings{ {Labels: labels}, {Labels: make(map[string]string)}, }, } app = App{ ID: "test-service", Tasks: []Task{task}, RunningTasks: runningTasks, Labels: labels, Container: container, } ) return &AppList{ Apps: []App{app}, } } func TestMarathonSDSendGroupWithContainerPortMappings(t *testing.T) { var ( ch = make(chan []*targetgroup.Group, 1) client = func(client *http.Client, url, token string) (*AppList, error) { return marathonTestAppListWithContainerPortMappings(marathonValidLabel, 1), nil } ) if err := testUpdateServices(client, ch); err != nil { t.Fatalf("Got error: %s", err) } select { case tgs := <-ch: tg := tgs[0] if tg.Source != "test-service" { t.Fatalf("Wrong target group name: %s", tg.Source) } if len(tg.Targets) != 2 { t.Fatalf("Wrong number of targets: %v", tg.Targets) } tgt := tg.Targets[0] if tgt[model.AddressLabel] != "mesos-slave1:31000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) } tgt = tg.Targets[1] if tgt[model.AddressLabel] != "mesos-slave1:32000" { t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) } if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) } default: t.Fatal("Did not get a target group.") } } prometheus-2.1.0+ds/discovery/openstack/000077500000000000000000000000001323116307200203035ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/openstack/hypervisor.go000066400000000000000000000107701323116307200230510ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "context" "fmt" "net" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors" "github.com/gophercloud/gophercloud/pagination" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" ) const ( openstackLabelHypervisorHostIP = openstackLabelPrefix + "hypervisor_host_ip" openstackLabelHypervisorHostName = openstackLabelPrefix + "hypervisor_hostname" openstackLabelHypervisorStatus = openstackLabelPrefix + "hypervisor_status" openstackLabelHypervisorState = openstackLabelPrefix + "hypervisor_state" openstackLabelHypervisorType = openstackLabelPrefix + "hypervisor_type" ) // HypervisorDiscovery discovers OpenStack hypervisors. type HypervisorDiscovery struct { authOpts *gophercloud.AuthOptions region string interval time.Duration logger log.Logger port int } // NewHypervisorDiscovery returns a new hypervisor discovery. func NewHypervisorDiscovery(opts *gophercloud.AuthOptions, interval time.Duration, port int, region string, l log.Logger) *HypervisorDiscovery { return &HypervisorDiscovery{authOpts: opts, region: region, interval: interval, port: port, logger: l} } // Run implements the Discoverer interface. func (h *HypervisorDiscovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Get an initial set right away. tg, err := h.refresh() if err != nil { level.Error(h.logger).Log("msg", "Unable refresh target groups", "err", err.Error()) } else { select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } } ticker := time.NewTicker(h.interval) defer ticker.Stop() for { select { case <-ticker.C: tg, err := h.refresh() if err != nil { level.Error(h.logger).Log("msg", "Unable refresh target groups", "err", err.Error()) continue } select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } case <-ctx.Done(): return } } } func (h *HypervisorDiscovery) refresh() (*targetgroup.Group, error) { var err error t0 := time.Now() defer func() { refreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { refreshFailuresCount.Inc() } }() provider, err := openstack.AuthenticatedClient(*h.authOpts) if err != nil { return nil, fmt.Errorf("could not create OpenStack session: %s", err) } client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ Region: h.region, }) if err != nil { return nil, fmt.Errorf("could not create OpenStack compute session: %s", err) } tg := &targetgroup.Group{ Source: fmt.Sprintf("OS_" + h.region), } // OpenStack API reference // https://developer.openstack.org/api-ref/compute/#list-hypervisors-details pagerHypervisors := hypervisors.List(client) err = pagerHypervisors.EachPage(func(page pagination.Page) (bool, error) { hypervisorList, err := hypervisors.ExtractHypervisors(page) if err != nil { return false, fmt.Errorf("could not extract hypervisors: %s", err) } for _, hypervisor := range hypervisorList { labels := model.LabelSet{ openstackLabelHypervisorHostIP: model.LabelValue(hypervisor.HostIP), } addr := net.JoinHostPort(hypervisor.HostIP, fmt.Sprintf("%d", h.port)) labels[model.AddressLabel] = model.LabelValue(addr) labels[openstackLabelHypervisorHostName] = model.LabelValue(hypervisor.HypervisorHostname) labels[openstackLabelHypervisorHostIP] = model.LabelValue(hypervisor.HostIP) labels[openstackLabelHypervisorStatus] = model.LabelValue(hypervisor.Status) labels[openstackLabelHypervisorState] = model.LabelValue(hypervisor.State) labels[openstackLabelHypervisorType] = model.LabelValue(hypervisor.HypervisorType) tg.Targets = append(tg.Targets, labels) } return true, nil }) if err != nil { return nil, err } return tg, nil } prometheus-2.1.0+ds/discovery/openstack/hypervisor_test.go000066400000000000000000000060321323116307200241040ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/prometheus/common/model" ) type OpenstackSDHypervisorTestSuite struct { suite.Suite Mock *SDMock } func (s *OpenstackSDHypervisorTestSuite) TearDownSuite() { s.Mock.ShutdownServer() } func (s *OpenstackSDHypervisorTestSuite) SetupTest() { s.Mock = NewSDMock(s.T()) s.Mock.Setup() s.Mock.HandleHypervisorListSuccessfully() s.Mock.HandleVersionsSuccessfully() s.Mock.HandleAuthSuccessfully() } func TestOpenstackSDHypervisorSuite(t *testing.T) { suite.Run(t, new(OpenstackSDHypervisorTestSuite)) } func (s *OpenstackSDHypervisorTestSuite) openstackAuthSuccess() (Discovery, error) { conf := SDConfig{ IdentityEndpoint: s.Mock.Endpoint(), Password: "test", Username: "test", DomainName: "12345", Region: "RegionOne", Role: "hypervisor", } return NewDiscovery(&conf, nil) } func (s *OpenstackSDHypervisorTestSuite) TestOpenstackSDHypervisorRefresh() { hypervisor, _ := s.openstackAuthSuccess() tg, err := hypervisor.refresh() assert.Nil(s.T(), err) require.NotNil(s.T(), tg) require.NotNil(s.T(), tg.Targets) require.Len(s.T(), tg.Targets, 2) assert.Equal(s.T(), tg.Targets[0]["__address__"], model.LabelValue("172.16.70.14:0")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_hostname"], model.LabelValue("nc14.cloud.com")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.14")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_state"], model.LabelValue("up")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled")) assert.Equal(s.T(), tg.Targets[1]["__address__"], model.LabelValue("172.16.70.13:0")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_hostname"], model.LabelValue("cc13.cloud.com")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.13")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_state"], model.LabelValue("up")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled")) } prometheus-2.1.0+ds/discovery/openstack/instance.go000066400000000000000000000146331323116307200224450ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "context" "fmt" "net" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/pagination" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" ) const ( openstackLabelPrefix = model.MetaLabelPrefix + "openstack_" openstackLabelInstanceID = openstackLabelPrefix + "instance_id" openstackLabelInstanceName = openstackLabelPrefix + "instance_name" openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status" openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor" openstackLabelPublicIP = openstackLabelPrefix + "public_ip" openstackLabelPrivateIP = openstackLabelPrefix + "private_ip" openstackLabelTagPrefix = openstackLabelPrefix + "tag_" ) // InstanceDiscovery discovers OpenStack instances. type InstanceDiscovery struct { authOpts *gophercloud.AuthOptions region string interval time.Duration logger log.Logger port int } // NewInstanceDiscovery returns a new instance discovery. func NewInstanceDiscovery(opts *gophercloud.AuthOptions, interval time.Duration, port int, region string, l log.Logger) *InstanceDiscovery { if l == nil { l = log.NewNopLogger() } return &InstanceDiscovery{authOpts: opts, region: region, interval: interval, port: port, logger: l} } // Run implements the Discoverer interface. func (i *InstanceDiscovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { // Get an initial set right away. tg, err := i.refresh() if err != nil { level.Error(i.logger).Log("msg", "Unable to refresh target groups", "err", err.Error()) } else { select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } } ticker := time.NewTicker(i.interval) defer ticker.Stop() for { select { case <-ticker.C: tg, err := i.refresh() if err != nil { level.Error(i.logger).Log("msg", "Unable to refresh target groups", "err", err.Error()) continue } select { case ch <- []*targetgroup.Group{tg}: case <-ctx.Done(): return } case <-ctx.Done(): return } } } func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) { var err error t0 := time.Now() defer func() { refreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { refreshFailuresCount.Inc() } }() provider, err := openstack.AuthenticatedClient(*i.authOpts) if err != nil { return nil, fmt.Errorf("could not create OpenStack session: %s", err) } client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ Region: i.region, }) if err != nil { return nil, fmt.Errorf("could not create OpenStack compute session: %s", err) } // OpenStack API reference // https://developer.openstack.org/api-ref/compute/#list-floating-ips pagerFIP := floatingips.List(client) floatingIPList := make(map[string][]string) err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) { result, err := floatingips.ExtractFloatingIPs(page) if err != nil { return false, fmt.Errorf("could not extract floatingips: %s", err) } for _, ip := range result { // Skip not associated ips if ip.InstanceID != "" { floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP) } } return true, nil }) if err != nil { return nil, err } // OpenStack API reference // https://developer.openstack.org/api-ref/compute/#list-servers opts := servers.ListOpts{} pager := servers.List(client, opts) tg := &targetgroup.Group{ Source: fmt.Sprintf("OS_" + i.region), } err = pager.EachPage(func(page pagination.Page) (bool, error) { instanceList, err := servers.ExtractServers(page) if err != nil { return false, fmt.Errorf("could not extract instances: %s", err) } for _, s := range instanceList { labels := model.LabelSet{ openstackLabelInstanceID: model.LabelValue(s.ID), } if len(s.Addresses) == 0 { level.Info(i.logger).Log("msg", "Got no IP address", "instance", s.ID) continue } for _, address := range s.Addresses { md, ok := address.([]interface{}) if !ok { level.Warn(i.logger).Log("msg", "Invalid type for address, expected array") continue } if len(md) == 0 { level.Debug(i.logger).Log("msg", "Got no IP address", "instance", s.ID) continue } md1, ok := md[0].(map[string]interface{}) if !ok { level.Warn(i.logger).Log("msg", "Invalid type for address, expected dict") continue } addr, ok := md1["addr"].(string) if !ok { level.Warn(i.logger).Log("msg", "Invalid type for address, expected string") continue } labels[openstackLabelPrivateIP] = model.LabelValue(addr) addr = net.JoinHostPort(addr, fmt.Sprintf("%d", i.port)) labels[model.AddressLabel] = model.LabelValue(addr) // Only use first private IP break } if val, ok := floatingIPList[s.ID]; ok && len(val) > 0 { labels[openstackLabelPublicIP] = model.LabelValue(val[0]) } labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status) labels[openstackLabelInstanceName] = model.LabelValue(s.Name) id, ok := s.Flavor["id"].(string) if !ok { level.Warn(i.logger).Log("msg", "Invalid type for instance id, excepted string") continue } labels[openstackLabelInstanceFlavor] = model.LabelValue(id) for k, v := range s.Metadata { name := strutil.SanitizeLabelName(k) labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v) } tg.Targets = append(tg.Targets, labels) } return true, nil }) if err != nil { return nil, err } return tg, nil } prometheus-2.1.0+ds/discovery/openstack/instance_test.go000066400000000000000000000062001323116307200234730ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/prometheus/common/model" ) type OpenstackSDInstanceTestSuite struct { suite.Suite Mock *SDMock } func (s *OpenstackSDInstanceTestSuite) TearDownSuite() { s.Mock.ShutdownServer() } func (s *OpenstackSDInstanceTestSuite) SetupTest() { s.Mock = NewSDMock(s.T()) s.Mock.Setup() s.Mock.HandleServerListSuccessfully() s.Mock.HandleFloatingIPListSuccessfully() s.Mock.HandleVersionsSuccessfully() s.Mock.HandleAuthSuccessfully() } func TestOpenstackSDInstanceSuite(t *testing.T) { suite.Run(t, new(OpenstackSDInstanceTestSuite)) } func (s *OpenstackSDInstanceTestSuite) openstackAuthSuccess() (Discovery, error) { conf := SDConfig{ IdentityEndpoint: s.Mock.Endpoint(), Password: "test", Username: "test", DomainName: "12345", Region: "RegionOne", Role: "instance", } return NewDiscovery(&conf, nil) } func (s *OpenstackSDInstanceTestSuite) TestOpenstackSDInstanceRefresh() { instance, _ := s.openstackAuthSuccess() tg, err := instance.refresh() assert.Nil(s.T(), err) require.NotNil(s.T(), tg) require.NotNil(s.T(), tg.Targets) require.Len(s.T(), tg.Targets, 3) assert.Equal(s.T(), tg.Targets[0]["__address__"], model.LabelValue("10.0.0.32:0")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_flavor"], model.LabelValue("1")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_id"], model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_name"], model.LabelValue("herp")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.32")) assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_public_ip"], model.LabelValue("10.10.10.2")) assert.Equal(s.T(), tg.Targets[1]["__address__"], model.LabelValue("10.0.0.31:0")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_flavor"], model.LabelValue("1")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_id"], model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682ba")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_name"], model.LabelValue("derp")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.31")) } prometheus-2.1.0+ds/discovery/openstack/mock.go000066400000000000000000000433711323116307200215730ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "fmt" "net/http" "net/http/httptest" "testing" ) // SDMock is the interface for the OpenStack mock type SDMock struct { t *testing.T Server *httptest.Server Mux *http.ServeMux } // NewSDMock returns a new SDMock. func NewSDMock(t *testing.T) *SDMock { return &SDMock{ t: t, } } // Endpoint returns the URI to the mock server func (m *SDMock) Endpoint() string { return m.Server.URL + "/" } // Setup creates the mock server func (m *SDMock) Setup() { m.Mux = http.NewServeMux() m.Server = httptest.NewServer(m.Mux) } // ShutdownServer creates the mock server func (m *SDMock) ShutdownServer() { m.Server.Close() } const tokenID = "cbc36478b0bd8e67e89469c7749d4127" func testMethod(t *testing.T, r *http.Request, expected string) { if expected != r.Method { t.Errorf("Request method = %v, expected %v", r.Method, expected) } } func testHeader(t *testing.T, r *http.Request, header string, expected string) { if actual := r.Header.Get(header); expected != actual { t.Errorf("Header %s = %s, expected %s", header, actual, expected) } } // HandleVersionsSuccessfully mocks version call func (m *SDMock) HandleVersionsSuccessfully() { m.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ` { "versions": { "values": [ { "status": "stable", "id": "v3.0", "links": [ { "href": "%s", "rel": "self" } ] }, { "status": "stable", "id": "v2.0", "links": [ { "href": "%s", "rel": "self" } ] } ] } } `, m.Endpoint()+"v3/", m.Endpoint()+"v2.0/") }) } // HandleAuthSuccessfully mocks auth call func (m *SDMock) HandleAuthSuccessfully() { m.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("X-Subject-Token", tokenID) w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ` { "token": { "audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"], "catalog": [ { "endpoints": [ { "id": "39dc322ce86c4111b4f06c2eeae0841b", "interface": "public", "region": "RegionOne", "url": "http://localhost:5000" }, { "id": "ec642f27474842e78bf059f6c48f4e99", "interface": "internal", "region": "RegionOne", "url": "http://localhost:5000" }, { "id": "c609fc430175452290b62a4242e8a7e8", "interface": "admin", "region": "RegionOne", "url": "http://localhost:35357" } ], "id": "4363ae44bdf34a3981fde3b823cb9aa2", "type": "identity", "name": "keystone" }, { "endpoints": [ { "id": "e2ffee808abc4a60916715b1d4b489dd", "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "url": "%s" } ], "id": "b7f2a5b1a019459cb956e43a8cb41e31", "type": "compute" } ], "expires_at": "2013-02-27T18:30:59.999999Z", "is_domain": false, "issued_at": "2013-02-27T16:30:59.999999Z", "methods": [ "password" ], "project": { "domain": { "id": "1789d1", "name": "example.com" }, "id": "263fd9", "name": "project-x" }, "roles": [ { "id": "76e72a", "name": "admin" }, { "id": "f4f392", "name": "member" } ], "user": { "domain": { "id": "1789d1", "name": "example.com" }, "id": "0ca8f6", "name": "Joe", "password_expires_at": "2016-11-06T15:32:17.000000" } } } `, m.Endpoint()) }) } const hypervisorListBody = ` { "hypervisors": [ { "status": "enabled", "service": { "host": "nc14.cloud.com", "disabled_reason": null, "id": 16 }, "vcpus_used": 18, "hypervisor_type": "QEMU", "local_gb_used": 84, "vcpus": 24, "hypervisor_hostname": "nc14.cloud.com", "memory_mb_used": 24064, "memory_mb": 96484, "current_workload": 1, "state": "up", "host_ip": "172.16.70.14", "cpu_info": "{\"vendor\": \"Intel\", \"model\": \"IvyBridge\", \"arch\": \"x86_64\", \"features\": [\"pge\", \"avx\", \"clflush\", \"sep\", \"syscall\", \"vme\", \"dtes64\", \"msr\", \"fsgsbase\", \"xsave\", \"vmx\", \"erms\", \"xtpr\", \"cmov\", \"smep\", \"ssse3\", \"est\", \"pat\", \"monitor\", \"smx\", \"pbe\", \"lm\", \"tsc\", \"nx\", \"fxsr\", \"tm\", \"sse4.1\", \"pae\", \"sse4.2\", \"pclmuldq\", \"acpi\", \"tsc-deadline\", \"mmx\", \"osxsave\", \"cx8\", \"mce\", \"de\", \"tm2\", \"ht\", \"dca\", \"lahf_lm\", \"popcnt\", \"mca\", \"pdpe1gb\", \"apic\", \"sse\", \"f16c\", \"pse\", \"ds\", \"invtsc\", \"pni\", \"rdtscp\", \"aes\", \"sse2\", \"ss\", \"ds_cpl\", \"pcid\", \"fpu\", \"cx16\", \"pse36\", \"mtrr\", \"pdcm\", \"rdrand\", \"x2apic\"], \"topology\": {\"cores\": 6, \"cells\": 2, \"threads\": 2, \"sockets\": 1}}", "running_vms": 10, "free_disk_gb": 315, "hypervisor_version": 2003000, "disk_available_least": 304, "local_gb": 399, "free_ram_mb": 72420, "id": 1 }, { "status": "enabled", "service": { "host": "cc13.cloud.com", "disabled_reason": null, "id": 17 }, "vcpus_used": 1, "hypervisor_type": "QEMU", "local_gb_used": 20, "vcpus": 24, "hypervisor_hostname": "cc13.cloud.com", "memory_mb_used": 2560, "memory_mb": 96484, "current_workload": 0, "state": "up", "host_ip": "172.16.70.13", "cpu_info": "{\"vendor\": \"Intel\", \"model\": \"IvyBridge\", \"arch\": \"x86_64\", \"features\": [\"pge\", \"avx\", \"clflush\", \"sep\", \"syscall\", \"vme\", \"dtes64\", \"msr\", \"fsgsbase\", \"xsave\", \"vmx\", \"erms\", \"xtpr\", \"cmov\", \"smep\", \"ssse3\", \"est\", \"pat\", \"monitor\", \"smx\", \"pbe\", \"lm\", \"tsc\", \"nx\", \"fxsr\", \"tm\", \"sse4.1\", \"pae\", \"sse4.2\", \"pclmuldq\", \"acpi\", \"tsc-deadline\", \"mmx\", \"osxsave\", \"cx8\", \"mce\", \"de\", \"tm2\", \"ht\", \"dca\", \"lahf_lm\", \"popcnt\", \"mca\", \"pdpe1gb\", \"apic\", \"sse\", \"f16c\", \"pse\", \"ds\", \"invtsc\", \"pni\", \"rdtscp\", \"aes\", \"sse2\", \"ss\", \"ds_cpl\", \"pcid\", \"fpu\", \"cx16\", \"pse36\", \"mtrr\", \"pdcm\", \"rdrand\", \"x2apic\"], \"topology\": {\"cores\": 6, \"cells\": 2, \"threads\": 2, \"sockets\": 1}}", "running_vms": 0, "free_disk_gb": 379, "hypervisor_version": 2003000, "disk_available_least": 384, "local_gb": 399, "free_ram_mb": 93924, "id": 721 } ] }` // HandleHypervisorListSuccessfully mocks os-hypervisors detail call func (m *SDMock) HandleHypervisorListSuccessfully() { m.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) { testMethod(m.t, r, "GET") testHeader(m.t, r, "X-Auth-Token", tokenID) w.Header().Add("Content-Type", "application/json") fmt.Fprintf(w, hypervisorListBody) }) } const serverListBody = ` { "servers": [ { "status": "ERROR", "updated": "2014-09-25T13:10:10Z", "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", "OS-EXT-SRV-ATTR:host": "devstack", "addresses": {}, "links": [ { "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/af9bcad9-3c87-477d-9347-b291eabf480e", "rel": "self" }, { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/af9bcad9-3c87-477d-9347-b291eabf480e", "rel": "bookmark" } ], "key_name": null, "image": { "id": "f90f6034-2570-4974-8351-6b49732ef2eb", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb", "rel": "bookmark" } ] }, "OS-EXT-STS:task_state": null, "OS-EXT-STS:vm_state": "error", "OS-EXT-SRV-ATTR:instance_name": "instance-00000010", "OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000", "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "flavor": { "id": "1", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", "rel": "bookmark" } ] }, "id": "af9bcad9-3c87-477d-9347-b291eabf480e", "security_groups": [ { "name": "default" } ], "OS-SRV-USG:terminated_at": null, "OS-EXT-AZ:availability_zone": "nova", "user_id": "9349aff8be7545ac9d2f1d00999a23cd", "name": "herp2", "created": "2014-09-25T13:10:02Z", "tenant_id": "fcad67a6189847c4aecfa3c81a05783b", "OS-DCF:diskConfig": "MANUAL", "os-extended-volumes:volumes_attached": [], "accessIPv4": "", "accessIPv6": "", "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", "metadata": {} }, { "status": "ACTIVE", "updated": "2014-09-25T13:10:10Z", "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", "OS-EXT-SRV-ATTR:host": "devstack", "addresses": { "private": [ { "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b", "version": 4, "addr": "10.0.0.32", "OS-EXT-IPS:type": "fixed" } ] }, "links": [ { "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "rel": "self" }, { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "rel": "bookmark" } ], "key_name": null, "image": { "id": "f90f6034-2570-4974-8351-6b49732ef2eb", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb", "rel": "bookmark" } ] }, "OS-EXT-STS:task_state": null, "OS-EXT-STS:vm_state": "active", "OS-EXT-SRV-ATTR:instance_name": "instance-0000001e", "OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000", "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "flavor": { "id": "1", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", "rel": "bookmark" } ] }, "id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "security_groups": [ { "name": "default" } ], "OS-SRV-USG:terminated_at": null, "OS-EXT-AZ:availability_zone": "nova", "user_id": "9349aff8be7545ac9d2f1d00999a23cd", "name": "herp", "created": "2014-09-25T13:10:02Z", "tenant_id": "fcad67a6189847c4aecfa3c81a05783b", "OS-DCF:diskConfig": "MANUAL", "os-extended-volumes:volumes_attached": [], "accessIPv4": "", "accessIPv6": "", "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", "metadata": {} }, { "status": "ACTIVE", "updated": "2014-09-25T13:04:49Z", "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", "OS-EXT-SRV-ATTR:host": "devstack", "addresses": { "private": [ { "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be", "version": 4, "addr": "10.0.0.31", "OS-EXT-IPS:type": "fixed" } ] }, "links": [ { "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", "rel": "self" }, { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", "rel": "bookmark" } ], "key_name": null, "image": { "id": "f90f6034-2570-4974-8351-6b49732ef2eb", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb", "rel": "bookmark" } ] }, "OS-EXT-STS:task_state": null, "OS-EXT-STS:vm_state": "active", "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d", "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000", "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "flavor": { "id": "1", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", "rel": "bookmark" } ] }, "id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba", "security_groups": [ { "name": "default" } ], "OS-SRV-USG:terminated_at": null, "OS-EXT-AZ:availability_zone": "nova", "user_id": "9349aff8be7545ac9d2f1d00999a23cd", "name": "derp", "created": "2014-09-25T13:04:41Z", "tenant_id": "fcad67a6189847c4aecfa3c81a05783b", "OS-DCF:diskConfig": "MANUAL", "os-extended-volumes:volumes_attached": [], "accessIPv4": "", "accessIPv6": "", "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", "metadata": {} }, { "status": "ACTIVE", "updated": "2014-09-25T13:04:49Z", "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362", "OS-EXT-SRV-ATTR:host": "devstack", "addresses": { "private": [ { "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be", "version": 4, "addr": "10.0.0.31", "OS-EXT-IPS:type": "fixed" } ] }, "links": [ { "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", "rel": "self" }, { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba", "rel": "bookmark" } ], "key_name": null, "image": "", "OS-EXT-STS:task_state": null, "OS-EXT-STS:vm_state": "active", "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d", "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000", "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "flavor": { "id": "1", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", "rel": "bookmark" } ] }, "id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb", "security_groups": [ { "name": "default" } ], "OS-SRV-USG:terminated_at": null, "OS-EXT-AZ:availability_zone": "nova", "user_id": "9349aff8be7545ac9d2f1d00999a23cd", "name": "merp", "created": "2014-09-25T13:04:41Z", "tenant_id": "fcad67a6189847c4aecfa3c81a05783b", "OS-DCF:diskConfig": "MANUAL", "os-extended-volumes:volumes_attached": [], "accessIPv4": "", "accessIPv6": "", "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", "metadata": {} } ] } ` // HandleServerListSuccessfully mocks server detail call func (m *SDMock) HandleServerListSuccessfully() { m.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) { testMethod(m.t, r, "GET") testHeader(m.t, r, "X-Auth-Token", tokenID) w.Header().Add("Content-Type", "application/json") fmt.Fprintf(w, serverListBody) }) } const listOutput = ` { "floating_ips": [ { "fixed_ip": null, "id": "1", "instance_id": null, "ip": "10.10.10.1", "pool": "nova" }, { "fixed_ip": "166.78.185.201", "id": "2", "instance_id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "ip": "10.10.10.2", "pool": "nova" } ] } ` // HandleFloatingIPListSuccessfully mocks floating ips call func (m *SDMock) HandleFloatingIPListSuccessfully() { m.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) { testMethod(m.t, r, "GET") testHeader(m.t, r, "X-Auth-Token", tokenID) w.Header().Add("Content-Type", "application/json") fmt.Fprintf(w, listOutput) }) } prometheus-2.1.0+ds/discovery/openstack/openstack.go000066400000000000000000000117671323116307200226350ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openstack import ( "context" "errors" "fmt" "time" "github.com/go-kit/kit/log" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" yaml_util "github.com/prometheus/prometheus/util/yaml" ) var ( refreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_openstack_refresh_failures_total", Help: "The number of OpenStack-SD scrape failures.", }) refreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_openstack_refresh_duration_seconds", Help: "The duration of an OpenStack-SD refresh in seconds.", }) // DefaultSDConfig is the default OpenStack SD configuration. DefaultSDConfig = SDConfig{ Port: 80, RefreshInterval: model.Duration(60 * time.Second), } ) // SDConfig is the configuration for OpenStack based service discovery. type SDConfig struct { IdentityEndpoint string `yaml:"identity_endpoint"` Username string `yaml:"username"` UserID string `yaml:"userid"` Password config_util.Secret `yaml:"password"` ProjectName string `yaml:"project_name"` ProjectID string `yaml:"project_id"` DomainName string `yaml:"domain_name"` DomainID string `yaml:"domain_id"` Role Role `yaml:"role"` Region string `yaml:"region"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` Port int `yaml:"port"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // OpenStackRole is role of the target in OpenStack. type Role string // The valid options for OpenStackRole. const ( // OpenStack document reference // https://docs.openstack.org/nova/pike/admin/arch.html#hypervisors OpenStackRoleHypervisor Role = "hypervisor" // OpenStack document reference // https://docs.openstack.org/horizon/pike/user/launch-instances.html OpenStackRoleInstance Role = "instance" ) // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*string)(c)); err != nil { return err } switch *c { case OpenStackRoleHypervisor, OpenStackRoleInstance: return nil default: return fmt.Errorf("Unknown OpenStack SD role %q", *c) } } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if c.Role == "" { return fmt.Errorf("role missing (one of: instance, hypervisor)") } return yaml_util.CheckOverflow(c.XXX, "openstack_sd_config") } func init() { prometheus.MustRegister(refreshFailuresCount) prometheus.MustRegister(refreshDuration) } // Discovery periodically performs OpenStack-SD requests. It implements // the Discoverer interface. type Discovery interface { Run(ctx context.Context, ch chan<- []*targetgroup.Group) refresh() (tg *targetgroup.Group, err error) } // NewDiscovery returns a new OpenStackDiscovery which periodically refreshes its targets. func NewDiscovery(conf *SDConfig, l log.Logger) (Discovery, error) { var opts gophercloud.AuthOptions if conf.IdentityEndpoint == "" { var err error opts, err = openstack.AuthOptionsFromEnv() if err != nil { return nil, err } } else { opts = gophercloud.AuthOptions{ IdentityEndpoint: conf.IdentityEndpoint, Username: conf.Username, UserID: conf.UserID, Password: string(conf.Password), TenantName: conf.ProjectName, TenantID: conf.ProjectID, DomainName: conf.DomainName, DomainID: conf.DomainID, } } switch conf.Role { case OpenStackRoleHypervisor: hypervisor := NewHypervisorDiscovery(&opts, time.Duration(conf.RefreshInterval), conf.Port, conf.Region, l) return hypervisor, nil case OpenStackRoleInstance: instance := NewInstanceDiscovery(&opts, time.Duration(conf.RefreshInterval), conf.Port, conf.Region, l) return instance, nil default: return nil, errors.New("unknown OpenStack discovery role") } } prometheus-2.1.0+ds/discovery/targetgroup/000077500000000000000000000000001323116307200206575ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/targetgroup/targetgroup.go000066400000000000000000000053201323116307200235510ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package targetgroup import ( "encoding/json" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/util/yaml" ) // Group is a set of targets with a common label set(production , test, staging etc.). type Group struct { // Targets is a list of targets identified by a label set. Each target is // uniquely identifiable in the group by its address label. Targets []model.LabelSet // Labels is a set of labels that is common across all targets in the group. Labels model.LabelSet // Source is an identifier that describes a group of targets. Source string } func (tg Group) String() string { return tg.Source } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (tg *Group) UnmarshalYAML(unmarshal func(interface{}) error) error { g := struct { Targets []string `yaml:"targets"` Labels model.LabelSet `yaml:"labels"` XXX map[string]interface{} `yaml:",inline"` }{} if err := unmarshal(&g); err != nil { return err } tg.Targets = make([]model.LabelSet, 0, len(g.Targets)) for _, t := range g.Targets { tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: model.LabelValue(t), }) } tg.Labels = g.Labels return yaml.CheckOverflow(g.XXX, "static_config") } // MarshalYAML implements the yaml.Marshaler interface. func (tg Group) MarshalYAML() (interface{}, error) { g := &struct { Targets []string `yaml:"targets"` Labels model.LabelSet `yaml:"labels,omitempty"` }{ Targets: make([]string, 0, len(tg.Targets)), Labels: tg.Labels, } for _, t := range tg.Targets { g.Targets = append(g.Targets, string(t[model.AddressLabel])) } return g, nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (tg *Group) UnmarshalJSON(b []byte) error { g := struct { Targets []string `json:"targets"` Labels model.LabelSet `json:"labels"` }{} if err := json.Unmarshal(b, &g); err != nil { return err } tg.Targets = make([]model.LabelSet, 0, len(g.Targets)) for _, t := range g.Targets { tg.Targets = append(tg.Targets, model.LabelSet{ model.AddressLabel: model.LabelValue(t), }) } tg.Labels = g.Labels return nil } prometheus-2.1.0+ds/discovery/triton/000077500000000000000000000000001323116307200176335ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/triton/triton.go000066400000000000000000000153461323116307200215120ustar00rootroot00000000000000// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package triton import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/mwitkow/go-conntrack" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" config_util "github.com/prometheus/common/config" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/httputil" yaml_util "github.com/prometheus/prometheus/util/yaml" ) const ( tritonLabel = model.MetaLabelPrefix + "triton_" tritonLabelMachineID = tritonLabel + "machine_id" tritonLabelMachineAlias = tritonLabel + "machine_alias" tritonLabelMachineBrand = tritonLabel + "machine_brand" tritonLabelMachineImage = tritonLabel + "machine_image" tritonLabelServerID = tritonLabel + "server_id" namespace = "prometheus" ) var ( refreshFailuresCount = prometheus.NewCounter( prometheus.CounterOpts{ Name: "prometheus_sd_triton_refresh_failures_total", Help: "The number of Triton-SD scrape failures.", }) refreshDuration = prometheus.NewSummary( prometheus.SummaryOpts{ Name: "prometheus_sd_triton_refresh_duration_seconds", Help: "The duration of a Triton-SD refresh in seconds.", }) // DefaultSDConfig is the default Triton SD configuration. DefaultSDConfig = SDConfig{ Port: 9163, RefreshInterval: model.Duration(60 * time.Second), Version: 1, } ) // SDConfig is the configuration for Triton based service discovery. type SDConfig struct { Account string `yaml:"account"` DNSSuffix string `yaml:"dns_suffix"` Endpoint string `yaml:"endpoint"` Port int `yaml:"port"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"` Version int `yaml:"version"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultSDConfig type plain SDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if c.Account == "" { return fmt.Errorf("Triton SD configuration requires an account") } if c.DNSSuffix == "" { return fmt.Errorf("Triton SD configuration requires a dns_suffix") } if c.Endpoint == "" { return fmt.Errorf("Triton SD configuration requires an endpoint") } if c.RefreshInterval <= 0 { return fmt.Errorf("Triton SD configuration requires RefreshInterval to be a positive integer") } return yaml_util.CheckOverflow(c.XXX, "triton_sd_config") } func init() { prometheus.MustRegister(refreshFailuresCount) prometheus.MustRegister(refreshDuration) } // DiscoveryResponse models a JSON response from the Triton discovery. type DiscoveryResponse struct { Containers []struct { ServerUUID string `json:"server_uuid"` VMAlias string `json:"vm_alias"` VMBrand string `json:"vm_brand"` VMImageUUID string `json:"vm_image_uuid"` VMUUID string `json:"vm_uuid"` } `json:"containers"` } // Discovery periodically performs Triton-SD requests. It implements // the Discoverer interface. type Discovery struct { client *http.Client interval time.Duration logger log.Logger sdConfig *SDConfig } // New returns a new Discovery which periodically refreshes its targets. func New(logger log.Logger, conf *SDConfig) (*Discovery, error) { if logger == nil { logger = log.NewNopLogger() } tls, err := httputil.NewTLSConfig(conf.TLSConfig) if err != nil { return nil, err } transport := &http.Transport{ TLSClientConfig: tls, DialContext: conntrack.NewDialContextFunc( conntrack.DialWithTracing(), conntrack.DialWithName("triton_sd"), ), } client := &http.Client{Transport: transport} return &Discovery{ client: client, interval: time.Duration(conf.RefreshInterval), logger: logger, sdConfig: conf, }, nil } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { defer close(ch) ticker := time.NewTicker(d.interval) defer ticker.Stop() // Get an initial set right away. tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refreshing targets failed", "err", err) } else { ch <- []*targetgroup.Group{tg} } for { select { case <-ticker.C: tg, err := d.refresh() if err != nil { level.Error(d.logger).Log("msg", "Refreshing targets failed", "err", err) } else { ch <- []*targetgroup.Group{tg} } case <-ctx.Done(): return } } } func (d *Discovery) refresh() (tg *targetgroup.Group, err error) { t0 := time.Now() defer func() { refreshDuration.Observe(time.Since(t0).Seconds()) if err != nil { refreshFailuresCount.Inc() } }() var endpoint = fmt.Sprintf("https://%s:%d/v%d/discover", d.sdConfig.Endpoint, d.sdConfig.Port, d.sdConfig.Version) tg = &targetgroup.Group{ Source: endpoint, } resp, err := d.client.Get(endpoint) if err != nil { return tg, fmt.Errorf("an error occurred when requesting targets from the discovery endpoint. %s", err) } defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { return tg, fmt.Errorf("an error occurred when reading the response body. %s", err) } dr := DiscoveryResponse{} err = json.Unmarshal(data, &dr) if err != nil { return tg, fmt.Errorf("an error occurred unmarshaling the disovery response json. %s", err) } for _, container := range dr.Containers { labels := model.LabelSet{ tritonLabelMachineID: model.LabelValue(container.VMUUID), tritonLabelMachineAlias: model.LabelValue(container.VMAlias), tritonLabelMachineBrand: model.LabelValue(container.VMBrand), tritonLabelMachineImage: model.LabelValue(container.VMImageUUID), tritonLabelServerID: model.LabelValue(container.ServerUUID), } addr := fmt.Sprintf("%s.%s:%d", container.VMUUID, d.sdConfig.DNSSuffix, d.sdConfig.Port) labels[model.AddressLabel] = model.LabelValue(addr) tg.Targets = append(tg.Targets, labels) } return tg, nil } prometheus-2.1.0+ds/discovery/triton/triton_test.go000066400000000000000000000103761323116307200225470ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package triton import ( "context" "fmt" "net" "net/http" "net/http/httptest" "net/url" "strconv" "testing" "time" "github.com/stretchr/testify/assert" "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery/targetgroup" ) var ( conf = SDConfig{ Account: "testAccount", DNSSuffix: "triton.example.com", Endpoint: "127.0.0.1", Port: 443, Version: 1, RefreshInterval: 1, TLSConfig: config.TLSConfig{InsecureSkipVerify: true}, } badconf = SDConfig{ Account: "badTestAccount", DNSSuffix: "bad.triton.example.com", Endpoint: "127.0.0.1", Port: 443, Version: 1, RefreshInterval: 1, TLSConfig: config.TLSConfig{ InsecureSkipVerify: false, KeyFile: "shouldnotexist.key", CAFile: "shouldnotexist.ca", CertFile: "shouldnotexist.cert", }, } ) func TestTritonSDNew(t *testing.T) { td, err := New(nil, &conf) assert.Nil(t, err) assert.NotNil(t, td) assert.NotNil(t, td.client) assert.NotNil(t, td.interval) assert.NotNil(t, td.sdConfig) assert.Equal(t, conf.Account, td.sdConfig.Account) assert.Equal(t, conf.DNSSuffix, td.sdConfig.DNSSuffix) assert.Equal(t, conf.Endpoint, td.sdConfig.Endpoint) assert.Equal(t, conf.Port, td.sdConfig.Port) } func TestTritonSDNewBadConfig(t *testing.T) { td, err := New(nil, &badconf) assert.NotNil(t, err) assert.Nil(t, td) } func TestTritonSDRun(t *testing.T) { var ( td, err = New(nil, &conf) ch = make(chan []*targetgroup.Group) ctx, cancel = context.WithCancel(context.Background()) ) assert.Nil(t, err) assert.NotNil(t, td) go td.Run(ctx, ch) select { case <-time.After(60 * time.Millisecond): // Expected. case tgs := <-ch: t.Fatalf("Unexpected target groups in triton discovery: %s", tgs) } cancel() } func TestTritonSDRefreshNoTargets(t *testing.T) { tgts := testTritonSDRefresh(t, "{\"containers\":[]}") assert.Nil(t, tgts) } func TestTritonSDRefreshMultipleTargets(t *testing.T) { var ( dstr = `{"containers":[ { "server_uuid":"44454c4c-5000-104d-8037-b7c04f5a5131", "vm_alias":"server01", "vm_brand":"lx", "vm_image_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7", "vm_uuid":"ad466fbf-46a2-4027-9b64-8d3cdb7e9072" }, { "server_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", "vm_alias":"server02", "vm_brand":"kvm", "vm_image_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", "vm_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7" }] }` ) tgts := testTritonSDRefresh(t, dstr) assert.NotNil(t, tgts) assert.Equal(t, 2, len(tgts)) } func TestTritonSDRefreshNoServer(t *testing.T) { var ( td, err = New(nil, &conf) ) assert.Nil(t, err) assert.NotNil(t, td) tg, rerr := td.refresh() assert.NotNil(t, rerr) assert.Contains(t, rerr.Error(), "an error occurred when requesting targets from the discovery endpoint.") assert.NotNil(t, tg) assert.Nil(t, tg.Targets) } func testTritonSDRefresh(t *testing.T, dstr string) []model.LabelSet { var ( td, err = New(nil, &conf) s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, dstr) })) ) defer s.Close() u, uperr := url.Parse(s.URL) assert.Nil(t, uperr) assert.NotNil(t, u) host, strport, sherr := net.SplitHostPort(u.Host) assert.Nil(t, sherr) assert.NotNil(t, host) assert.NotNil(t, strport) port, atoierr := strconv.Atoi(strport) assert.Nil(t, atoierr) assert.NotNil(t, port) td.sdConfig.Port = port assert.Nil(t, err) assert.NotNil(t, td) tg, err := td.refresh() assert.Nil(t, err) assert.NotNil(t, tg) return tg.Targets } prometheus-2.1.0+ds/discovery/zookeeper/000077500000000000000000000000001323116307200203175ustar00rootroot00000000000000prometheus-2.1.0+ds/discovery/zookeeper/zookeeper.go000066400000000000000000000212061323116307200226520ustar00rootroot00000000000000// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zookeeper import ( "context" "encoding/json" "fmt" "net" "strconv" "strings" "time" "github.com/go-kit/kit/log" "github.com/prometheus/common/model" "github.com/samuel/go-zookeeper/zk" "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" "github.com/prometheus/prometheus/util/treecache" yaml_util "github.com/prometheus/prometheus/util/yaml" ) var ( // DefaultServersetSDConfig is the default Serverset SD configuration. DefaultServersetSDConfig = ServersetSDConfig{ Timeout: model.Duration(10 * time.Second), } // DefaultNerveSDConfig is the default Nerve SD configuration. DefaultNerveSDConfig = NerveSDConfig{ Timeout: model.Duration(10 * time.Second), } ) // ServersetSDConfig is the configuration for Twitter serversets in Zookeeper based discovery. type ServersetSDConfig struct { Servers []string `yaml:"servers"` Paths []string `yaml:"paths"` Timeout model.Duration `yaml:"timeout,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *ServersetSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultServersetSDConfig type plain ServersetSDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "serverset_sd_config"); err != nil { return err } if len(c.Servers) == 0 { return fmt.Errorf("serverset SD config must contain at least one Zookeeper server") } if len(c.Paths) == 0 { return fmt.Errorf("serverset SD config must contain at least one path") } for _, path := range c.Paths { if !strings.HasPrefix(path, "/") { return fmt.Errorf("serverset SD config paths must begin with '/': %s", path) } } return nil } // NerveSDConfig is the configuration for AirBnB's Nerve in Zookeeper based discovery. type NerveSDConfig struct { Servers []string `yaml:"servers"` Paths []string `yaml:"paths"` Timeout model.Duration `yaml:"timeout,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *NerveSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultNerveSDConfig type plain NerveSDConfig err := unmarshal((*plain)(c)) if err != nil { return err } if err := yaml_util.CheckOverflow(c.XXX, "nerve_sd_config"); err != nil { return err } if len(c.Servers) == 0 { return fmt.Errorf("nerve SD config must contain at least one Zookeeper server") } if len(c.Paths) == 0 { return fmt.Errorf("nerve SD config must contain at least one path") } for _, path := range c.Paths { if !strings.HasPrefix(path, "/") { return fmt.Errorf("nerve SD config paths must begin with '/': %s", path) } } return nil } // Discovery implements the Discoverer interface for discovering // targets from Zookeeper. type Discovery struct { conn *zk.Conn sources map[string]*targetgroup.Group updates chan treecache.ZookeeperTreeCacheEvent treeCaches []*treecache.ZookeeperTreeCache parse func(data []byte, path string) (model.LabelSet, error) logger log.Logger } // NewNerveDiscovery returns a new Discovery for the given Nerve config. func NewNerveDiscovery(conf *NerveSDConfig, logger log.Logger) *Discovery { return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, logger, parseNerveMember) } // NewServersetDiscovery returns a new Discovery for the given serverset config. func NewServersetDiscovery(conf *ServersetSDConfig, logger log.Logger) *Discovery { return NewDiscovery(conf.Servers, time.Duration(conf.Timeout), conf.Paths, logger, parseServersetMember) } // NewDiscovery returns a new discovery along Zookeeper parses with // the given parse function. func NewDiscovery( srvs []string, timeout time.Duration, paths []string, logger log.Logger, pf func(data []byte, path string) (model.LabelSet, error), ) *Discovery { if logger == nil { logger = log.NewNopLogger() } conn, _, err := zk.Connect(srvs, timeout) conn.SetLogger(treecache.NewZookeeperLogger(logger)) if err != nil { return nil } updates := make(chan treecache.ZookeeperTreeCacheEvent) sd := &Discovery{ conn: conn, updates: updates, sources: map[string]*targetgroup.Group{}, parse: pf, logger: logger, } for _, path := range paths { sd.treeCaches = append(sd.treeCaches, treecache.NewZookeeperTreeCache(conn, path, updates, logger)) } return sd } // Run implements the Discoverer interface. func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { defer func() { for _, tc := range d.treeCaches { tc.Stop() } // Drain event channel in case the treecache leaks goroutines otherwise. for range d.updates { } d.conn.Close() }() for { select { case <-ctx.Done(): return case event := <-d.updates: tg := &targetgroup.Group{ Source: event.Path, } if event.Data != nil { labelSet, err := d.parse(*event.Data, event.Path) if err == nil { tg.Targets = []model.LabelSet{labelSet} d.sources[event.Path] = tg } else { delete(d.sources, event.Path) } } else { delete(d.sources, event.Path) } select { case <-ctx.Done(): return case ch <- []*targetgroup.Group{tg}: } } } } const ( serversetLabelPrefix = model.MetaLabelPrefix + "serverset_" serversetStatusLabel = serversetLabelPrefix + "status" serversetPathLabel = serversetLabelPrefix + "path" serversetEndpointLabelPrefix = serversetLabelPrefix + "endpoint" serversetShardLabel = serversetLabelPrefix + "shard" ) type serversetMember struct { ServiceEndpoint serversetEndpoint AdditionalEndpoints map[string]serversetEndpoint Status string `json:"status"` Shard int `json:"shard"` } type serversetEndpoint struct { Host string Port int } func parseServersetMember(data []byte, path string) (model.LabelSet, error) { member := serversetMember{} if err := json.Unmarshal(data, &member); err != nil { return nil, fmt.Errorf("error unmarshaling serverset member %q: %s", path, err) } labels := model.LabelSet{} labels[serversetPathLabel] = model.LabelValue(path) labels[model.AddressLabel] = model.LabelValue( net.JoinHostPort(member.ServiceEndpoint.Host, fmt.Sprintf("%d", member.ServiceEndpoint.Port))) labels[serversetEndpointLabelPrefix+"_host"] = model.LabelValue(member.ServiceEndpoint.Host) labels[serversetEndpointLabelPrefix+"_port"] = model.LabelValue(fmt.Sprintf("%d", member.ServiceEndpoint.Port)) for name, endpoint := range member.AdditionalEndpoints { cleanName := model.LabelName(strutil.SanitizeLabelName(name)) labels[serversetEndpointLabelPrefix+"_host_"+cleanName] = model.LabelValue( endpoint.Host) labels[serversetEndpointLabelPrefix+"_port_"+cleanName] = model.LabelValue( fmt.Sprintf("%d", endpoint.Port)) } labels[serversetStatusLabel] = model.LabelValue(member.Status) labels[serversetShardLabel] = model.LabelValue(strconv.Itoa(member.Shard)) return labels, nil } const ( nerveLabelPrefix = model.MetaLabelPrefix + "nerve_" nervePathLabel = nerveLabelPrefix + "path" nerveEndpointLabelPrefix = nerveLabelPrefix + "endpoint" ) type nerveMember struct { Host string `json:"host"` Port int `json:"port"` Name string `json:"name"` } func parseNerveMember(data []byte, path string) (model.LabelSet, error) { member := nerveMember{} err := json.Unmarshal(data, &member) if err != nil { return nil, fmt.Errorf("error unmarshaling nerve member %q: %s", path, err) } labels := model.LabelSet{} labels[nervePathLabel] = model.LabelValue(path) labels[model.AddressLabel] = model.LabelValue( net.JoinHostPort(member.Host, fmt.Sprintf("%d", member.Port))) labels[nerveEndpointLabelPrefix+"_host"] = model.LabelValue(member.Host) labels[nerveEndpointLabelPrefix+"_port"] = model.LabelValue(fmt.Sprintf("%d", member.Port)) labels[nerveEndpointLabelPrefix+"_name"] = model.LabelValue(member.Name) return labels, nil } prometheus-2.1.0+ds/docs/000077500000000000000000000000001323116307200152355ustar00rootroot00000000000000prometheus-2.1.0+ds/docs/configuration/000077500000000000000000000000001323116307200201045ustar00rootroot00000000000000prometheus-2.1.0+ds/docs/configuration/alerting_rules.md000066400000000000000000000102411323116307200234430ustar00rootroot00000000000000--- title: Alerting rules sort_rank: 3 --- # Alerting rules Alerting rules allow you to define alert conditions based on Prometheus expression language expressions and to send notifications about firing alerts to an external service. Whenever the alert expression results in one or more vector elements at a given point in time, the alert counts as active for these elements' label sets. ### Defining alerting rules Alerting rules are configured in Prometheus in the same way as [recording rules](recording_rules.md). An example rules file with an alert would be: ```yaml groups: - name: example rules: - alert: HighErrorRate expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 for: 10m labels: severity: page annotations: summary: High request latency ``` The optional `for` clause causes Prometheus to wait for a certain duration between first encountering a new expression output vector element and counting an alert as firing for this element. In this case, Prometheus will check that the alert continues to be active during each evaluation for 10 minutes before firing the alert. Elements that are active, but not firing yet, are in the pending state. The `labels` clause allows specifying a set of additional labels to be attached to the alert. Any existing conflicting labels will be overwritten. The label values can be templated. The `annotations` clause specifies a set of informational labels that can be used to store longer additional information such as alert descriptions or runbook links. The annotation values can be templated. #### Templating Label and annotation values can be templated using [console templates](https://prometheus.io/docs/visualization/consoles). The `$labels` variable holds the label key/value pairs of an alert instance and `$value` holds the evaluated value of an alert instance. # To insert a firing element's label values: {{ $labels. }} # To insert the numeric expression value of the firing element: {{ $value }} Examples: ```yaml groups: - name: example rules: # Alert for any instance that is unreachable for >5 minutes. - alert: InstanceDown expr: up == 0 for: 5m labels: severity: page annotations: summary: "Instance {{ $labels.instance }} down" description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." # Alert for any instance that has a median request latency >1s. - alert: APIHighRequestLatency expr: api_http_request_latencies_second{quantile="0.5"} > 1 for: 10m annotations: summary: "High request latency on {{ $labels.instance }}" description: "{{ $labels.instance }} has a median request latency above 1s (current value: {{ $value }}s)" ``` ### Inspecting alerts during runtime To manually inspect which alerts are active (pending or firing), navigate to the "Alerts" tab of your Prometheus instance. This will show you the exact label sets for which each defined alert is currently active. For pending and firing alerts, Prometheus also stores synthetic time series of the form `ALERTS{alertname="", alertstate="pending|firing", }`. The sample value is set to `1` as long as the alert is in the indicated active (pending or firing) state, and a single `0` value gets written out when an alert transitions from active to inactive state. Once inactive, the time series does not get further updates. ### Sending alert notifications Prometheus's alerting rules are good at figuring what is broken *right now*, but they are not a fully-fledged notification solution. Another layer is needed to add summarization, notification rate limiting, silencing and alert dependencies on top of the simple alert definitions. In Prometheus's ecosystem, the [Alertmanager](https://prometheus.io/docs/alerting/alertmanager/) takes on this role. Thus, Prometheus may be configured to periodically send information about alert states to an Alertmanager instance, which then takes care of dispatching the right notifications. Prometheus can be [configured](configuration.md) to automatically discovered available Alertmanager instances through its service discovery integrations. prometheus-2.1.0+ds/docs/configuration/configuration.md000066400000000000000000001247741323116307200233140ustar00rootroot00000000000000--- title: Configuration sort_rank: 1 --- # Configuration Prometheus is configured via command-line flags and a configuration file. While the command-line flags configure immutable system parameters (such as storage locations, amount of data to keep on disk and in memory, etc.), the configuration file defines everything related to scraping [jobs and their instances](https://prometheus.io/docs/concepts/jobs_instances/), as well as which [rule files to load](recording_rules.md#configuring-rules). To view all available command-line flags, run `./prometheus -h`. Prometheus can reload its configuration at runtime. If the new configuration is not well-formed, the changes will not be applied. A configuration reload is triggered by sending a `SIGHUP` to the Prometheus process or sending a HTTP POST request to the `/-/reload` endpoint (when the `--web.enable-lifecycle` flag is enabled). This will also reload any configured rule files. ## Configuration file To specify which configuration file to load, use the `--config.file` flag. The file is written in [YAML format](http://en.wikipedia.org/wiki/YAML), defined by the scheme described below. Brackets indicate that a parameter is optional. For non-list parameters the value is set to the specified default. Generic placeholders are defined as follows: * ``: a boolean that can take the values `true` or `false` * ``: a duration matching the regular expression `[0-9]+(ms|[smhdwy])` * ``: a string matching the regular expression `[a-zA-Z_][a-zA-Z0-9_]*` * ``: a string of unicode characters * ``: a valid path in the current working directory * ``: a valid string consisting of a hostname or IP followed by an optional port number * ``: a valid URL path * ``: a string that can take the values `http` or `https` * ``: a regular string * ``: a regular string that is a secret, such as a password * ``: a string which is template-expanded before usage The other placeholders are specified separately. A valid example file can be found [here](/config/testdata/conf.good.yml). The global configuration specifies parameters that are valid in all other configuration contexts. They also serve as defaults for other configuration sections. ```yaml global: # How frequently to scrape targets by default. [ scrape_interval: | default = 1m ] # How long until a scrape request times out. [ scrape_timeout: | default = 10s ] # How frequently to evaluate rules. [ evaluation_interval: | default = 1m ] # The labels to add to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: [ : ... ] # Rule files specifies a list of globs. Rules and alerts are read from # all matching files. rule_files: [ - ... ] # A list of scrape configurations. scrape_configs: [ - ... ] # Alerting specifies settings related to the Alertmanager. alerting: alert_relabel_configs: [ - ... ] alertmanagers: [ - ... ] # Settings related to the remote write feature. remote_write: [ - ... ] # Settings related to the remote read feature. remote_read: [ - ... ] ``` ### `` A `scrape_config` section specifies a set of targets and parameters describing how to scrape them. In the general case, one scrape configuration specifies a single job. In advanced configurations, this may change. Targets may be statically configured via the `static_configs` parameter or dynamically discovered using one of the supported service-discovery mechanisms. Additionally, `relabel_configs` allow advanced modifications to any target and its labels before scraping. ```yaml # The job name assigned to scraped metrics by default. job_name: # How frequently to scrape targets from this job. [ scrape_interval: | default = ] # Per-scrape timeout when scraping this job. [ scrape_timeout: | default = ] # The HTTP resource path on which to fetch metrics from targets. [ metrics_path: | default = /metrics ] # honor_labels controls how Prometheus handles conflicts between labels that are # already present in scraped data and labels that Prometheus would attach # server-side ("job" and "instance" labels, manually configured target # labels, and labels generated by service discovery implementations). # # If honor_labels is set to "true", label conflicts are resolved by keeping label # values from the scraped data and ignoring the conflicting server-side labels. # # If honor_labels is set to "false", label conflicts are resolved by renaming # conflicting labels in the scraped data to "exported_" (for # example "exported_instance", "exported_job") and then attaching server-side # labels. This is useful for use cases such as federation, where all labels # specified in the target should be preserved. # # Note that any globally configured "external_labels" are unaffected by this # setting. In communication with external systems, they are always applied only # when a time series does not have a given label yet and are ignored otherwise. [ honor_labels: | default = false ] # Configures the protocol scheme used for requests. [ scheme: | default = http ] # Optional HTTP URL parameters. params: [ : [, ...] ] # Sets the `Authorization` header on every scrape request with the # configured username and password. basic_auth: [ username: ] [ password: ] # Sets the `Authorization` header on every scrape request with # the configured bearer token. It is mutually exclusive with `bearer_token_file`. [ bearer_token: ] # Sets the `Authorization` header on every scrape request with the bearer token # read from the configured file. It is mutually exclusive with `bearer_token`. [ bearer_token_file: /path/to/bearer/token/file ] # Configures the scrape request's TLS settings. tls_config: [ ] # Optional proxy URL. [ proxy_url: ] # List of Azure service discovery configurations. azure_sd_configs: [ - ... ] # List of Consul service discovery configurations. consul_sd_configs: [ - ... ] # List of DNS service discovery configurations. dns_sd_configs: [ - ... ] # List of EC2 service discovery configurations. ec2_sd_configs: [ - ... ] # List of OpenStack service discovery configurations. openstack_sd_configs: [ - ... ] # List of file service discovery configurations. file_sd_configs: [ - ... ] # List of GCE service discovery configurations. gce_sd_configs: [ - ... ] # List of Kubernetes service discovery configurations. kubernetes_sd_configs: [ - ... ] # List of Marathon service discovery configurations. marathon_sd_configs: [ - ... ] # List of AirBnB's Nerve service discovery configurations. nerve_sd_configs: [ - ... ] # List of Zookeeper Serverset service discovery configurations. serverset_sd_configs: [ - ... ] # List of Triton service discovery configurations. triton_sd_configs: [ - ... ] # List of labeled statically configured targets for this job. static_configs: [ - ... ] # List of target relabel configurations. relabel_configs: [ - ... ] # List of metric relabel configurations. metric_relabel_configs: [ - ... ] # Per-scrape limit on number of scraped samples that will be accepted. # If more than this number of samples are present after metric relabelling # the entire scrape will be treated as failed. 0 means no limit. [ sample_limit: | default = 0 ] ``` Where `` must be unique across all scrape configurations. ### `` A `tls_config` allows configuring TLS connections. ```yaml # CA certificate to validate API server certificate with. [ ca_file: ] # Certificate and key files for client cert authentication to the server. [ cert_file: ] [ key_file: ] # ServerName extension to indicate the name of the server. # http://tools.ietf.org/html/rfc4366#section-3.1 [ server_name: ] # Disable validation of the server certificate. [ insecure_skip_verify: ] ``` ### `` Azure SD configurations allow retrieving scrape targets from Azure VMs. The following meta labels are available on targets during relabeling: * `__meta_azure_machine_id`: the machine ID * `__meta_azure_machine_location`: the location the machine runs in * `__meta_azure_machine_name`: the machine name * `__meta_azure_machine_private_ip`: the machine's private IP * `__meta_azure_machine_resource_group`: the machine's resource group * `__meta_azure_machine_tag_`: each tag value of the machine See below for the configuration options for Azure discovery: ```yaml # The information to access the Azure API. # The subscription ID. subscription_id: # The tenant ID. tenant_id: # The client ID. client_id: # The client secret. client_secret: # Refresh interval to re-read the instance list. [ refresh_interval: | default = 300s ] # The port to scrape metrics from. If using the public IP address, this must # instead be specified in the relabeling rule. [ port: | default = 80 ] ``` ### `` Consul SD configurations allow retrieving scrape targets from [Consul's](https://www.consul.io) Catalog API. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_consul_address`: the address of the target * `__meta_consul_dc`: the datacenter name for the target * `__meta_consul_metadata_`: each metadata key value of the target * `__meta_consul_node`: the node name defined for the target * `__meta_consul_service_address`: the service address of the target * `__meta_consul_service_id`: the service ID of the target * `__meta_consul_service_port`: the service port of the target * `__meta_consul_service`: the name of the service the target belongs to * `__meta_consul_tags`: the list of tags of the target joined by the tag separator ```yaml # The information to access the Consul API. It is to be defined # as the Consul documentation requires. server: [ token: ] [ datacenter: ] [ scheme: | default = "http"] [ username: ] [ password: ] tls_config: [ ] # A list of services for which targets are retrieved. If omitted, all services # are scraped. services: [ - ] # The string by which Consul tags are joined into the tag label. [ tag_separator: | default = , ] ``` Note that the IP number and port used to scrape the targets is assembled as `<__meta_consul_address>:<__meta_consul_service_port>`. However, in some Consul setups, the relevant address is in `__meta_consul_service_address`. In those cases, you can use the [relabel](#relabel_config) feature to replace the special `__address__` label. ### `` A DNS-based service discovery configuration allows specifying a set of DNS domain names which are periodically queried to discover a list of targets. The DNS servers to be contacted are read from `/etc/resolv.conf`. This service discovery method only supports basic DNS A, AAAA and SRV record queries, but not the advanced DNS-SD approach specified in [RFC6763](https://tools.ietf.org/html/rfc6763). During the [relabeling phase](#relabel_config), the meta label `__meta_dns_name` is available on each target and is set to the record name that produced the discovered target. ```yaml # A list of DNS domain names to be queried. names: [ - ] # The type of DNS query to perform. [ type: | default = 'SRV' ] # The port number used if the query type is not SRV. [ port: ] # The time after which the provided names are refreshed. [ refresh_interval: | default = 30s ] ``` Where `` is a valid DNS domain name. Where `` is `SRV`, `A`, or `AAAA`. ### `` EC2 SD configurations allow retrieving scrape targets from AWS EC2 instances. The private IP address is used by default, but may be changed to the public IP address with relabeling. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_ec2_availability_zone`: the availability zone in which the instance is running * `__meta_ec2_instance_id`: the EC2 instance ID * `__meta_ec2_instance_state`: the state of the EC2 instance * `__meta_ec2_instance_type`: the type of the EC2 instance * `__meta_ec2_private_ip`: the private IP address of the instance, if present * `__meta_ec2_public_dns_name`: the public DNS name of the instance, if available * `__meta_ec2_public_ip`: the public IP address of the instance, if available * `__meta_ec2_subnet_id`: comma separated list of subnets IDs in which the instance is running, if available * `__meta_ec2_tag_`: each tag value of the instance * `__meta_ec2_vpc_id`: the ID of the VPC in which the instance is running, if available See below for the configuration options for EC2 discovery: ```yaml # The information to access the EC2 API. # The AWS Region. region: # The AWS API keys. If blank, the environment variables `AWS_ACCESS_KEY_ID` # and `AWS_SECRET_ACCESS_KEY` are used. [ access_key: ] [ secret_key: ] # Named AWS profile used to connect to the API. [ profile: ] # AWS Role ARN, an alternative to using AWS API keys. [ role_arn: ] # Refresh interval to re-read the instance list. [ refresh_interval: | default = 60s ] # The port to scrape metrics from. If using the public IP address, this must # instead be specified in the relabeling rule. [ port: | default = 80 ] ``` ### `` OpenStack SD configurations allow retrieving scrape targets from OpenStack Nova instances. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_openstack_instance_id`: the OpenStack instance ID. * `__meta_openstack_instance_name`: the OpenStack instance name. * `__meta_openstack_instance_status`: the status of the OpenStack instance. * `__meta_openstack_instance_flavor`: the flavor of the OpenStack instance. * `__meta_openstack_public_ip`: the public IP of the OpenStack instance. * `__meta_openstack_private_ip`: the private IP of the OpenStack instance. * `__meta_openstack_tag_`: each tag value of the instance. #### `instance` The `instance` role discovers one target per Nova instance. The target address defaults to the first private IP address of the instance. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_openstack_instance_id`: the OpenStack instance ID. * `__meta_openstack_instance_name`: the OpenStack instance name. * `__meta_openstack_instance_status`: the status of the OpenStack instance. * `__meta_openstack_instance_flavor`: the flavor of the OpenStack instance. * `__meta_openstack_public_ip`: the public IP of the OpenStack instance. * `__meta_openstack_private_ip`: the private IP of the OpenStack instance. * `__meta_openstack_tag_`: each tag value of the instance. See below for the configuration options for OpenStack discovery: ```yaml # The information to access the OpenStack API. # The OpenStack role of entities that should be discovered. role: # The OpenStack Region. region: # identity_endpoint specifies the HTTP endpoint that is required to work with # the Identity API of the appropriate version. While it's ultimately needed by # all of the identity services, it will often be populated by a provider-level # function. [ identity_endpoint: ] # username is required if using Identity V2 API. Consult with your provider's # control panel to discover your account's username. In Identity V3, either # userid or a combination of username and domain_id or domain_name are needed. [ username: ] [ userid: ] # password for the Identity V2 and V3 APIs. Consult with your provider's # control panel to discover your account's preferred method of authentication. [ password: ] # At most one of domain_id and domain_name must be provided if using username # with Identity V3. Otherwise, either are optional. [ domain_name: ] [ domain_id: ] # The project_id and project_name fields are optional for the Identity V2 API. # Some providers allow you to specify a project_name instead of the project_id. # Some require both. Your provider's authentication policies will determine # how these fields influence authentication. [ project_name: ] [ project_id: ] # Refresh interval to re-read the instance list. [ refresh_interval: | default = 60s ] # The port to scrape metrics from. If using the public IP address, this must # instead be specified in the relabeling rule. [ port: | default = 80 ] ``` ### `` File-based service discovery provides a more generic way to configure static targets and serves as an interface to plug in custom service discovery mechanisms. It reads a set of files containing a list of zero or more ``s. Changes to all defined files are detected via disk watches and applied immediately. Files may be provided in YAML or JSON format. Only changes resulting in well-formed target groups are applied. The JSON file must contain a list of static configs, using this format: ```yaml [ { "targets": [ "", ... ], "labels": { "": "", ... } }, ... ] ``` As a fallback, the file contents are also re-read periodically at the specified refresh interval. Each target has a meta label `__meta_filepath` during the [relabeling phase](#relabel_config). Its value is set to the filepath from which the target was extracted. There is a list of [integrations](https://prometheus.io/docs/operating/integrations/#file-service-discovery) with this discovery mechanism. ```yaml # Patterns for files from which target groups are extracted. files: [ - ... ] # Refresh interval to re-read the files. [ refresh_interval: | default = 5m ] ``` Where `` may be a path ending in `.json`, `.yml` or `.yaml`. The last path segment may contain a single `*` that matches any character sequence, e.g. `my/path/tg_*.json`. ### `` [GCE](https://cloud.google.com/compute/) SD configurations allow retrieving scrape targets from GCP GCE instances. The private IP address is used by default, but may be changed to the public IP address with relabeling. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_gce_instance_name`: the name of the instance * `__meta_gce_metadata_`: each metadata item of the instance * `__meta_gce_network`: the network URL of the instance * `__meta_gce_private_ip`: the private IP address of the instance * `__meta_gce_project`: the GCP project in which the instance is running * `__meta_gce_public_ip`: the public IP address of the instance, if present * `__meta_gce_subnetwork`: the subnetwork URL of the instance * `__meta_gce_tags`: comma separated list of instance tags * `__meta_gce_zone`: the GCE zone URL in which the instance is running See below for the configuration options for GCE discovery: ```yaml # The information to access the GCE API. # The GCP Project project: # The zone of the scrape targets. If you need multiple zones use multiple # gce_sd_configs. zone: # Filter can be used optionally to filter the instance list by other criteria # Syntax of this filter string is described here in the filter query parameter section: # https://cloud.google.com/compute/docs/reference/latest/instances/list [ filter: ] # Refresh interval to re-read the instance list [ refresh_interval: | default = 60s ] # The port to scrape metrics from. If using the public IP address, this must # instead be specified in the relabeling rule. [ port: | default = 80 ] # The tag separator is used to separate the tags on concatenation [ tag_separator: | default = , ] ``` Credentials are discovered by the Google Cloud SDK default client by looking in the following places, preferring the first location found: 1. a JSON file specified by the `GOOGLE_APPLICATION_CREDENTIALS` environment variable 2. a JSON file in the well-known path `$HOME/.config/gcloud/application_default_credentials.json` 3. fetched from the GCE metadata server If Prometheus is running within GCE, the service account associated with the instance it is running on should have at least read-only permissions to the compute resources. If running outside of GCE make sure to create an appropriate service account and place the credential file in one of the expected locations. ### `` Kubernetes SD configurations allow retrieving scrape targets from [Kubernetes'](http://kubernetes.io/) REST API and always staying synchronized with the cluster state. One of the following `role` types can be configured to discover targets: #### `node` The `node` role discovers one target per cluster node with the address defaulting to the Kubelet's HTTP port. The target address defaults to the first existing address of the Kubernetes node object in the address type order of `NodeInternalIP`, `NodeExternalIP`, `NodeLegacyHostIP`, and `NodeHostName`. Available meta labels: * `__meta_kubernetes_node_name`: The name of the node object. * `__meta_kubernetes_node_label_`: Each label from the node object. * `__meta_kubernetes_node_annotation_`: Each annotation from the node object. * `__meta_kubernetes_node_address_`: The first address for each node address type, if it exists. In addition, the `instance` label for the node will be set to the node name as retrieved from the API server. #### `service` The `service` role discovers a target for each service port for each service. This is generally useful for blackbox monitoring of a service. The address will be set to the Kubernetes DNS name of the service and respective service port. Available meta labels: * `__meta_kubernetes_namespace`: The namespace of the service object. * `__meta_kubernetes_service_name`: The name of the service object. * `__meta_kubernetes_service_label_`: The label of the service object. * `__meta_kubernetes_service_annotation_`: The annotation of the service object. * `__meta_kubernetes_service_port_name`: Name of the service port for the target. * `__meta_kubernetes_service_port_number`: Number of the service port for the target. * `__meta_kubernetes_service_port_protocol`: Protocol of the service port for the target. #### `pod` The `pod` role discovers all pods and exposes their containers as targets. For each declared port of a container, a single target is generated. If a container has no specified ports, a port-free target per container is created for manually adding a port via relabeling. Available meta labels: * `__meta_kubernetes_namespace`: The namespace of the pod object. * `__meta_kubernetes_pod_name`: The name of the pod object. * `__meta_kubernetes_pod_ip`: The pod IP of the pod object. * `__meta_kubernetes_pod_label_`: The label of the pod object. * `__meta_kubernetes_pod_annotation_`: The annotation of the pod object. * `__meta_kubernetes_pod_container_name`: Name of the container the target address points to. * `__meta_kubernetes_pod_container_port_name`: Name of the container port. * `__meta_kubernetes_pod_container_port_number`: Number of the container port. * `__meta_kubernetes_pod_container_port_protocol`: Protocol of the container port. * `__meta_kubernetes_pod_ready`: Set to `true` or `false` for the pod's ready state. * `__meta_kubernetes_pod_node_name`: The name of the node the pod is scheduled onto. * `__meta_kubernetes_pod_host_ip`: The current host IP of the pod object. * `__meta_kubernetes_pod_uid`: The UID of the pod object. #### `endpoints` The `endpoints` role discovers targets from listed endpoints of a service. For each endpoint address one target is discovered per port. If the endpoint is backed by a pod, all additional container ports of the pod, not bound to an endpoint port, are discovered as targets as well. Available meta labels: * `__meta_kubernetes_namespace`: The namespace of the endpoints object. * `__meta_kubernetes_endpoints_name`: The names of the endpoints object. * For all targets discovered directly from the endpoints list (those not additionally inferred from underlying pods), the following labels are attached: * `__meta_kubernetes_endpoint_ready`: Set to `true` or `false` for the endpoint's ready state. * `__meta_kubernetes_endpoint_port_name`: Name of the endpoint port. * `__meta_kubernetes_endpoint_port_protocol`: Protocol of the endpoint port. * If the endpoints belong to a service, all labels of the `role: service` discovery are attached. * For all targets backed by a pod, all labels of the `role: pod` discovery are attached. #### `ingress` The `ingress` role discovers a target for each path of each ingress. This is generally useful for blackbox monitoring of an ingress. The address will be set to the host specified in the ingress spec. Available meta labels: * `__meta_kubernetes_namespace`: The namespace of the ingress object. * `__meta_kubernetes_ingress_name`: The name of the ingress object. * `__meta_kubernetes_ingress_label_`: The label of the ingress object. * `__meta_kubernetes_ingress_annotation_`: The annotation of the ingress object. * `__meta_kubernetes_ingress_scheme`: Protocol scheme of ingress, `https` if TLS config is set. Defaults to `http`. * `__meta_kubernetes_ingress_path`: Path from ingress spec. Defaults to `/`. See below for the configuration options for Kubernetes discovery: ```yaml # The information to access the Kubernetes API. # The API server addresses. If left empty, Prometheus is assumed to run inside # of the cluster and will discover API servers automatically and use the pod's # CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/. [ api_server: ] # The Kubernetes role of entities that should be discovered. role: # Optional authentication information used to authenticate to the API server. # Note that `basic_auth`, `bearer_token` and `bearer_token_file` options are # mutually exclusive. # Optional HTTP basic authentication information. basic_auth: [ username: ] [ password: ] # Optional bearer token authentication information. [ bearer_token: ] # Optional bearer token file authentication information. [ bearer_token_file: ] # TLS configuration. tls_config: [ ] # Optional namespace discovery. If omitted, all namespaces are used. namespaces: names: [ - ] ``` Where `` must be `endpoints`, `service`, `pod`, `node`, or `ingress`. See [this example Prometheus configuration file](/documentation/examples/prometheus-kubernetes.yml) for a detailed example of configuring Prometheus for Kubernetes. You may wish to check out the 3rd party [Prometheus Operator](https://github.com/coreos/prometheus-operator), which automates the Prometheus setup on top of Kubernetes. ### `` Marathon SD configurations allow retrieving scrape targets using the [Marathon](https://mesosphere.github.io/marathon/) REST API. Prometheus will periodically check the REST endpoint for currently running tasks and create a target group for every app that has at least one healthy task. The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_marathon_app`: the name of the app (with slashes replaced by dashes) * `__meta_marathon_image`: the name of the Docker image used (if available) * `__meta_marathon_task`: the ID of the Mesos task * `__meta_marathon_app_label_`: any Marathon labels attached to the app * `__meta_marathon_port_definition_label_`: the port definition labels * `__meta_marathon_port_mapping_label_`: the port mapping labels * `__meta_marathon_port_index`: the port index number (e.g. `1` for `PORT1`) See below for the configuration options for Marathon discovery: ```yaml # List of URLs to be used to contact Marathon servers. # You need to provide at least one server URL. servers: - # Optional bearer token authentication information. # It is mutually exclusive with `bearer_token_file`. [ bearer_token: ] # Optional bearer token file authentication information. # It is mutually exclusive with `bearer_token`. [ bearer_token_file: ] # Polling interval [ refresh_interval: | default = 30s ] ``` By default every app listed in Marathon will be scraped by Prometheus. If not all of your services provide Prometheus metrics, you can use a Marathon label and Prometheus relabeling to control which instances will actually be scraped. Also by default all apps will show up as a single job in Prometheus (the one specified in the configuration file), which can also be changed using relabeling. ### `` Nerve SD configurations allow retrieving scrape targets from [AirBnB's Nerve] (https://github.com/airbnb/nerve) which are stored in [Zookeeper](https://zookeeper.apache.org/). The following meta labels are available on targets during [relabeling](#relabel_config): * `__meta_nerve_path`: the full path to the endpoint node in Zookeeper * `__meta_nerve_endpoint_host`: the host of the endpoint * `__meta_nerve_endpoint_port`: the port of the endpoint * `__meta_nerve_endpoint_name`: the name of the endpoint ```yaml # The Zookeeper servers. servers: - # Paths can point to a single service, or the root of a tree of services. paths: - [ timeout: | default = 10s ] ``` ### `` Serverset SD configurations allow retrieving scrape targets from [Serversets] (https://github.com/twitter/finagle/tree/master/finagle-serversets) which are stored in [Zookeeper](https://zookeeper.apache.org/). Serversets are commonly used by [Finagle](https://twitter.github.io/finagle/) and [Aurora](http://aurora.apache.org/). The following meta labels are available on targets during relabeling: * `__meta_serverset_path`: the full path to the serverset member node in Zookeeper * `__meta_serverset_endpoint_host`: the host of the default endpoint * `__meta_serverset_endpoint_port`: the port of the default endpoint * `__meta_serverset_endpoint_host_`: the host of the given endpoint * `__meta_serverset_endpoint_port_`: the port of the given endpoint * `__meta_serverset_shard`: the shard number of the member * `__meta_serverset_status`: the status of the member ```yaml # The Zookeeper servers. servers: - # Paths can point to a single serverset, or the root of a tree of serversets. paths: - [ timeout: | default = 10s ] ``` Serverset data must be in the JSON format, the Thrift format is not currently supported. ### `` [Triton](https://github.com/joyent/triton) SD configurations allow retrieving scrape targets from [Container Monitor](https://github.com/joyent/rfd/blob/master/rfd/0027/README.md) discovery endpoints. The following meta labels are available on targets during relabeling: * `__meta_triton_machine_id`: the UUID of the target container * `__meta_triton_machine_alias`: the alias of the target container * `__meta_triton_machine_image`: the target containers image type * `__meta_triton_machine_server_id`: the server UUID for the target container ```yaml # The information to access the Triton discovery API. # The account to use for discovering new target containers. account: # The DNS suffix which should be applied to target containers. dns_suffix: # The Triton discovery endpoint (e.g. 'cmon.us-east-3b.triton.zone'). This is # often the same value as dns_suffix. endpoint: # The port to use for discovery and metric scraping. [ port: | default = 9163 ] # The interval which should should be used for refreshing target containers. [ refresh_interval: | default = 60s ] # The Triton discovery API version. [ version: | default = 1 ] # TLS configuration. tls_config: [ ] ``` ### `` A `static_config` allows specifying a list of targets and a common label set for them. It is the canonical way to specify static targets in a scrape configuration. ```yaml # The targets specified by the static config. targets: [ - '' ] # Labels assigned to all metrics scraped from the targets. labels: [ : ... ] ``` ### `` Relabeling is a powerful tool to dynamically rewrite the label set of a target before it gets scraped. Multiple relabeling steps can be configured per scrape configuration. They are applied to the label set of each target in order of their appearance in the configuration file. Initially, aside from the configured per-target labels, a target's `job` label is set to the `job_name` value of the respective scrape configuration. The `__address__` label is set to the `:` address of the target. After relabeling, the `instance` label is set to the value of `__address__` by default if it was not set during relabeling. The `__scheme__` and `__metrics_path__` labels are set to the scheme and metrics path of the target respectively. The `__param_` label is set to the value of the first passed URL parameter called ``. Additional labels prefixed with `__meta_` may be available during the relabeling phase. They are set by the service discovery mechanism that provided the target and vary between mechanisms. Labels starting with `__` will be removed from the label set after relabeling is completed. If a relabeling step needs to store a label value only temporarily (as the input to a subsequent relabeling step), use the `__tmp` label name prefix. This prefix is guaranteed to never be used by Prometheus itself. ```yaml # The source labels select values from existing labels. Their content is concatenated # using the configured separator and matched against the configured regular expression # for the replace, keep, and drop actions. [ source_labels: '[' [, ...] ']' ] # Separator placed between concatenated source label values. [ separator: | default = ; ] # Label to which the resulting value is written in a replace action. # It is mandatory for replace actions. Regex capture groups are available. [ target_label: ] # Regular expression against which the extracted value is matched. [ regex: | default = (.*) ] # Modulus to take of the hash of the source label values. [ modulus: ] # Replacement value against which a regex replace is performed if the # regular expression matches. Regex capture groups are available. [ replacement: | default = $1 ] # Action to perform based on regex matching. [ action: | default = replace ] ``` `` is any valid [RE2 regular expression](https://github.com/google/re2/wiki/Syntax). It is required for the `replace`, `keep`, `drop`, `labelmap`,`labeldrop` and `labelkeep` actions. The regex is anchored on both ends. To un-anchor the regex, use `.*.*`. `` determines the relabeling action to take: * `replace`: Match `regex` against the concatenated `source_labels`. Then, set `target_label` to `replacement`, with match group references (`${1}`, `${2}`, ...) in `replacement` substituted by their value. If `regex` does not match, no replacement takes place. * `keep`: Drop targets for which `regex` does not match the concatenated `source_labels`. * `drop`: Drop targets for which `regex` matches the concatenated `source_labels`. * `hashmod`: Set `target_label` to the `modulus` of a hash of the concatenated `source_labels`. * `labelmap`: Match `regex` against all label names. Then copy the values of the matching labels to label names given by `replacement` with match group references (`${1}`, `${2}`, ...) in `replacement` substituted by their value. * `labeldrop`: Match `regex` against all label names. Any label that matches will be removed from the set of labels. * `labelkeep`: Match `regex` against all label names. Any label that does not match will be removed from the set of labels. Care must be taken with `labeldrop` and `labelkeep` to ensure that metrics are still uniquely labeled once the labels are removed. ### `` Metric relabeling is applied to samples as the last step before ingestion. It has the same configuration format and actions as target relabeling. Metric relabeling does not apply to automatically generated timeseries such as `up`. One use for this is to blacklist time series that are too expensive to ingest. ### `` Alert relabeling is applied to alerts before they are sent to the Alertmanager. It has the same configuration format and actions as target relabeling. Alert relabeling is applied after external labels. One use for this is ensuring a HA pair of Prometheus servers with different external labels send identical alerts. ### `` An `alertmanager_config` section specifies Alertmanager instances the Prometheus server sends alerts to. It also provides parameters to configure how to communicate with these Alertmanagers. Alertmanagers may be statically configured via the `static_configs` parameter or dynamically discovered using one of the supported service-discovery mechanisms. Additionally, `relabel_configs` allow selecting Alertmanagers from discovered entities and provide advanced modifications to the used API path, which is exposed through the `__alerts_path__` label. ```yaml # Per-target Alertmanager timeout when pushing alerts. [ timeout: | default = 10s ] # Prefix for the HTTP path alerts are pushed to. [ path_prefix: | default = / ] # Configures the protocol scheme used for requests. [ scheme: | default = http ] # Sets the `Authorization` header on every request with the # configured username and password. basic_auth: [ username: ] [ password: ] # Sets the `Authorization` header on every request with # the configured bearer token. It is mutually exclusive with `bearer_token_file`. [ bearer_token: ] # Sets the `Authorization` header on every request with the bearer token # read from the configured file. It is mutually exclusive with `bearer_token`. [ bearer_token_file: /path/to/bearer/token/file ] # Configures the scrape request's TLS settings. tls_config: [ ] # Optional proxy URL. [ proxy_url: ] # List of Azure service discovery configurations. azure_sd_configs: [ - ... ] # List of Consul service discovery configurations. consul_sd_configs: [ - ... ] # List of DNS service discovery configurations. dns_sd_configs: [ - ... ] # List of EC2 service discovery configurations. ec2_sd_configs: [ - ... ] # List of file service discovery configurations. file_sd_configs: [ - ... ] # List of GCE service discovery configurations. gce_sd_configs: [ - ... ] # List of Kubernetes service discovery configurations. kubernetes_sd_configs: [ - ... ] # List of Marathon service discovery configurations. marathon_sd_configs: [ - ... ] # List of AirBnB's Nerve service discovery configurations. nerve_sd_configs: [ - ... ] # List of Zookeeper Serverset service discovery configurations. serverset_sd_configs: [ - ... ] # List of Triton service discovery configurations. triton_sd_configs: [ - ... ] # List of labeled statically configured Alertmanagers. static_configs: [ - ... ] # List of Alertmanager relabel configurations. relabel_configs: [ - ... ] ``` ### `` `write_relabel_configs` is relabeling applied to samples before sending them to the remote endpoint. Write relabeling is applied after external labels. This could be used to limit which samples are sent. There is a [small demo](/documentation/examples/remote_storage) of how to use this functionality. ```yaml # The URL of the endpoint to send samples to. url: # Timeout for requests to the remote write endpoint. [ remote_timeout: | default = 30s ] # List of remote write relabel configurations. write_relabel_configs: [ - ... ] # Sets the `Authorization` header on every remote write request with the # configured username and password. basic_auth: [ username: ] [ password: ] # Sets the `Authorization` header on every remote write request with # the configured bearer token. It is mutually exclusive with `bearer_token_file`. [ bearer_token: ] # Sets the `Authorization` header on every remote write request with the bearer token # read from the configured file. It is mutually exclusive with `bearer_token`. [ bearer_token_file: /path/to/bearer/token/file ] # Configures the remote write request's TLS settings. tls_config: [ ] # Optional proxy URL. [ proxy_url: ] ``` There is a list of [integrations](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage) with this feature. ### `` ```yaml # The URL of the endpoint to query from. url: # An optional list of equality matchers which have to be # present in a selector to query the remote read endpoint. required_matchers: [ : ... ] # Timeout for requests to the remote read endpoint. [ remote_timeout: | default = 30s ] # Whether reads should be made for queries for time ranges that # the local storage should have complete data for. [ read_recent: | default = false ] # Sets the `Authorization` header on every remote read request with the # configured username and password. basic_auth: [ username: ] [ password: ] # Sets the `Authorization` header on every remote read request with # the configured bearer token. It is mutually exclusive with `bearer_token_file`. [ bearer_token: ] # Sets the `Authorization` header on every remote read request with the bearer token # read from the configured file. It is mutually exclusive with `bearer_token`. [ bearer_token_file: /path/to/bearer/token/file ] # Configures the remote read request's TLS settings. tls_config: [ ] # Optional proxy URL. [ proxy_url: ] ``` There is a list of [integrations](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage) with this feature. prometheus-2.1.0+ds/docs/configuration/index.md000066400000000000000000000000521323116307200215320ustar00rootroot00000000000000--- title: Configuration sort_rank: 3 --- prometheus-2.1.0+ds/docs/configuration/recording_rules.md000066400000000000000000000064731323116307200236260ustar00rootroot00000000000000--- title: Recording rules sort_rank: 2 --- # Defining recording rules ## Configuring rules Prometheus supports two types of rules which may be configured and then evaluated at regular intervals: recording rules and [alerting rules](alerting_rules.md). To include rules in Prometheus, create a file containing the necessary rule statements and have Prometheus load the file via the `rule_files` field in the [Prometheus configuration](configuration.md). Rule files use YAML. The rule files can be reloaded at runtime by sending `SIGHUP` to the Prometheus process. The changes are only applied if all rule files are well-formatted. ## Syntax-checking rules To quickly check whether a rule file is syntactically correct without starting a Prometheus server, install and run Prometheus's `promtool` command-line utility tool: ```bash go get github.com/prometheus/prometheus/cmd/promtool promtool check rules /path/to/example.rules.yml ``` When the file is syntactically valid, the checker prints a textual representation of the parsed rules to standard output and then exits with a `0` return status. If there are any syntax errors or invalid input arguments, it prints an error message to standard error and exits with a `1` return status. ## Recording rules Recording rules allow you to precompute frequently needed or computationally expensive expressions and save their result as a new set of time series. Querying the precomputed result will then often be much faster than executing the original expression every time it is needed. This is especially useful for dashboards, which need to query the same expression repeatedly every time they refresh. Recording and alerting rules exist in a rule group. Rules within a group are run sequentially at a regular interval. The syntax of a rule file is: ```yaml groups: [ - ] ``` A simple example rules file would be: ```yaml groups: - name: example rules: - record: job:http_inprogress_requests:sum expr: sum(http_inprogress_requests) by (job) ``` ### `` ``` # The name of the group. Must be unique within a file. name: # How often rules in the group are evaluated. [ interval: | default = global.evaluation_interval ] rules: [ - ... ] ``` ### `` The syntax for recording rules is: ``` # The name of the time series to output to. Must be a valid metric name. record: # The PromQL expression to evaluate. Every evaluation cycle this is # evaluated at the current time, and the result recorded as a new set of # time series with the metric name as given by 'record'. expr: # Labels to add or overwrite before storing the result. labels: [ : ] ``` The syntax for alerting rules is: ``` # The name of the alert. Must be a valid metric name. alert: # The PromQL expression to evaluate. Every evaluation cycle this is # evaluated at the current time, and all resultant time series become # pending/firing alerts. expr: # Alerts are considered firing once they have been returned for this long. # Alerts which have not yet fired for long enough are considered pending. [ for: | default = 0s ] # Labels to add or overwrite for each alert. labels: [ : ] # Annotations to add to each alert. annotations: [ : ] ``` prometheus-2.1.0+ds/docs/configuration/template_examples.md000066400000000000000000000070371323116307200241460ustar00rootroot00000000000000--- title: Template examples sort_rank: 4 --- # Template examples Prometheus supports templating in the annotations and labels of alerts, as well as in served console pages. Templates have the ability to run queries against the local database, iterate over data, use conditionals, format data, etc. The Prometheus templating language is based on the [Go templating](http://golang.org/pkg/text/template/) system. ## Simple alert field templates ``` alert: InstanceDown expr: up == 0 for: 5m labels: - severity: page annotations: - summary: "Instance {{$labels.instance}} down" - description: "{{$labels.instance}} of job {{$labels.job}} has been down for more than 5 minutes." ``` Alert field templates will be executed during every rule iteration for each alert that fires, so keep any queries and templates lightweight. If you have a need for more complicated templates for alerts, it is recommended to link to a console instead. ## Simple iteration This displays a list of instances, and whether they are up: ```go {{ range query "up" }} {{ .Labels.instance }} {{ .Value }} {{ end }} ``` The special `.` variable contains the value of the current sample for each loop iteration. ## Display one value ```go {{ with query "some_metric{instance='someinstance'}" }} {{ . | first | value | humanize }} {{ end }} ``` Go and Go's templating language are both strongly typed, so one must check that samples were returned to avoid an execution error. For example this could happen if a scrape or rule evaluation has not run yet, or a host was down. The included `prom_query_drilldown` template handles this, allows for formatting of results, and linking to the [expression browser](https://prometheus.io/docs/visualization/browser/). ## Using console URL parameters ```go {{ with printf "node_memory_MemTotal{job='node',instance='%s'}" .Params.instance | query }} {{ . | first | value | humanize1024}}B {{ end }} ``` If accessed as `console.html?instance=hostname`, `.Params.instance` will evaluate to `hostname`. ## Advanced iteration ```html
    Prometheus Up Ingested Samples Memory
    {{ .Labels.instance }} Yes{{ else }} class="alert-danger">No{{ end }} {{ template "prom_query_drilldown" (args (printf "irate(prometheus_tsdb_head_samples_appended_total{job='prometheus',instance='%s'}[5m])" .Labels.instance) "/s" "humanizeNoSmallPrefix") }} {{ template "prom_query_drilldown" (args (printf "process_resident_memory_bytes{job='prometheus',instance='%s'}" .Labels.instance) "B" "humanize1024")}}
    No devices found.
    {{ range printf "node_network_receive_bytes{job='node',instance='%s',device!='lo'}" .Params.instance | query | sortByLabel "device"}} {{ end }}
    {{ .Labels.device }}
    Received {{ with printf "rate(node_network_receive_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device | query }}{{ . | first | value | humanize }}B/s{{end}}
    Transmitted {{ with printf "rate(node_network_transmit_bytes{job='node',instance='%s',device='%s'}[5m])" .Labels.instance .Labels.device | query }}{{ . | first | value | humanize }}B/s{{end}}
    ``` Here we iterate over all network devices and display the network traffic for each. As the `range` action does not specify a variable, `.Params.instance` is not available inside the loop as `.` is now the loop variable. ## Defining reusable templates Prometheus supports defining templates that can be reused. This is particularly powerful when combined with [console library](template_reference.md#console-templates) support, allowing sharing of templates across consoles. ```go {{/* Define the template */}} {{define "myTemplate"}} do something {{end}} {{/* Use the template */}} {{template "myTemplate"}} ``` Templates are limited to one argument. The `args` function can be used to wrap multiple arguments. ```go {{define "myMultiArgTemplate"}} First argument: {{.arg0}} Second argument: {{.arg1}} {{end}} {{template "myMultiArgTemplate" (args 1 2)}} ``` prometheus-2.1.0+ds/docs/configuration/template_reference.md000066400000000000000000000131121323116307200242550ustar00rootroot00000000000000--- title: Template reference sort_rank: 5 --- # Template reference Prometheus supports templating in the annotations and labels of alerts, as well as in served console pages. Templates have the ability to run queries against the local database, iterate over data, use conditionals, format data, etc. The Prometheus templating language is based on the [Go templating](http://golang.org/pkg/text/template/) system. ## Data Structures The primary data structure for dealing with time series data is the sample, defined as: ```go type sample struct { Labels map[string]string Value float64 } ``` The metric name of the sample is encoded in a special `__name__` label in the `Labels` map. `[]sample` means a list of samples. `interface{}` in Go is similar to a void pointer in C. ## Functions In addition to the [default functions](http://golang.org/pkg/text/template/#hdr-Functions) provided by Go templating, Prometheus provides functions for easier processing of query results in templates. If functions are used in a pipeline, the pipeline value is passed as the last argument. ### Queries | Name | Arguments | Returns | Notes | | ------------- | ------------- | -------- | -------- | | query | query string | []sample | Queries the database, does not support returning range vectors. | | first | []sample | sample | Equivalent to `index a 0` | | label | label, sample | string | Equivalent to `index sample.Labels label` | | value | sample | float64 | Equivalent to `sample.Value` | | sortByLabel | label, []samples | []sample | Sorts the samples by the given label. Is stable. | `first`, `label` and `value` are intended to make query results easily usable in pipelines. ### Numbers | Name | Arguments | Returns | Notes | | ------------- | --------------| --------| --------- | | humanize | number | string | Converts a number to a more readable format, using [metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix). | humanize1024 | number | string | Like `humanize`, but uses 1024 as the base rather than 1000. | | humanizeDuration | number | string | Converts a duration in seconds to a more readable format. | | humanizeTimestamp | number | string | Converts a Unix timestamp in seconds to a more readable format. | Humanizing functions are intended to produce reasonable output for consumption by humans, and are not guaranteed to return the same results between Prometheus versions. ### Strings | Name | Arguments | Returns | Notes | | ------------- | ------------- | ------- | ----------- | | title | string | string | [strings.Title](http://golang.org/pkg/strings/#Title), capitalises first character of each word.| | toUpper | string | string | [strings.ToUpper](http://golang.org/pkg/strings/#ToUpper), converts all characters to upper case.| | toLower | string | string | [strings.ToLower](http://golang.org/pkg/strings/#ToLower), converts all characters to lower case.| | match | pattern, text | boolean | [regexp.MatchString](http://golang.org/pkg/regexp/#MatchString) Tests for a unanchored regexp match. | | reReplaceAll | pattern, replacement, text | string | [Regexp.ReplaceAllString](http://golang.org/pkg/regexp/#Regexp.ReplaceAllString) Regexp substitution, unanchored. | | graphLink | expr | string | Returns path to graph view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. | | tableLink | expr | string | Returns path to tabular ("Console") view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. | ### Others | Name | Arguments | Returns | Notes | | ------------- | ------------- | ------- | ----------- | | args | []interface{} | map[string]interface{} | This converts a list of objects to a map with keys arg0, arg1 etc. This is intended to allow multiple arguments to be passed to templates. | | tmpl | string, []interface{} | nothing | Like the built-in `template`, but allows non-literals as the template name. Note that the result is assumed to be safe, and will not be auto-escaped. Only available in consoles. | | safeHtml | string | string | Marks string as HTML not requiring auto-escaping. | ## Template type differences Each of the types of templates provide different information that can be used to parameterize templates, and have a few other differences. ### Alert field templates `.Value` and `.Labels` contain the alert value and labels. They are also exposed as the `$value` and `$labels` variables for convenience. ### Console templates Consoles are exposed on `/consoles/`, and sourced from the directory pointed to by the `-web.console.templates` flag. Console templates are rendered with [html/template](http://golang.org/pkg/html/template/), which provides auto-escaping. To bypass the auto-escaping use the `safe*` functions., URL parameters are available as a map in `.Params`. To access multiple URL parameters by the same name, `.RawParams` is a map of the list values for each parameter. The URL path is available in `.Path`, excluding the `/consoles/` prefix. Consoles also have access to all the templates defined with `{{define "templateName"}}...{{end}}` found in `*.lib` files in the directory pointed to by the `-web.console.libraries` flag. As this is a shared namespace, take care to avoid clashes with other users. Template names beginning with `prom`, `_prom`, and `__` are reserved for use by Prometheus, as are the functions listed above. prometheus-2.1.0+ds/docs/federation.md000066400000000000000000000062721323116307200177060ustar00rootroot00000000000000--- title: Federation sort_rank: 6 --- # Federation Federation allows a Prometheus server to scrape selected time series from another Prometheus server. ## Use cases There are different use cases for federation. Commonly, it is used to either achieve scalable Prometheus monitoring setups or to pull related metrics from one service's Prometheus into another. ### Hierarchical federation Hierarchical federation allows Prometheus to scale to environments with tens of data centers and millions of nodes. In this use case, the federation topology resembles a tree, with higher-level Prometheus servers collecting aggregated time series data from a larger number of subordinated servers. For example, a setup might consist of many per-datacenter Prometheus servers that collect data in high detail (instance-level drill-down), and a set of global Prometheus servers which collect and store only aggregated data (job-level drill-down) from those local servers. This provides an aggregate global view and detailed local views. ### Cross-service federation In cross-service federation, a Prometheus server of one service is configured to scrape selected data from another service's Prometheus server to enable alerting and queries against both datasets within a single server. For example, a cluster scheduler running multiple services might expose resource usage information (like memory and CPU usage) about service instances running on the cluster. On the other hand, a service running on that cluster will only expose application-specific service metrics. Often, these two sets of metrics are scraped by separate Prometheus servers. Using federation, the Prometheus server containing service-level metrics may pull in the cluster resource usage metrics about its specific service from the cluster Prometheus, so that both sets of metrics can be used within that server. ## Configuring federation On any given Prometheus server, the `/federate` endpoint allows retrieving the current value for a selected set of time series in that server. At least one `match[]` URL parameter must be specified to select the series to expose. Each `match[]` argument needs to specify an [instant vector selector](querying/basics.md#instant-vector-selectors) like `up` or `{job="api-server"}`. If multiple `match[]` parameters are provided, the union of all matched series is selected. To federate metrics from one server to another, configure your destination Prometheus server to scrape from the `/federate` endpoint of a source server, while also enabling the `honor_labels` scrape option (to not overwrite any labels exposed by the source server) and passing in the desired `match[]` parameters. For example, the following `scrape_config` federates any series with the label `job="prometheus"` or a metric name starting with `job:` from the Prometheus servers at `source-prometheus-{1,2,3}:9090` into the scraping Prometheus: ```yaml - job_name: 'federate' scrape_interval: 15s honor_labels: true metrics_path: '/federate' params: 'match[]': - '{job="prometheus"}' - '{__name__=~"job:.*"}' static_configs: - targets: - 'source-prometheus-1:9090' - 'source-prometheus-2:9090' - 'source-prometheus-3:9090' ``` prometheus-2.1.0+ds/docs/getting_started.md000066400000000000000000000214541323116307200207540ustar00rootroot00000000000000--- title: Getting started sort_rank: 1 --- # Getting started This guide is a "Hello World"-style tutorial which shows how to install, configure, and use Prometheus in a simple example setup. You will download and run Prometheus locally, configure it to scrape itself and an example application, and then work with queries, rules, and graphs to make use of the collected time series data. ## Downloading and running Prometheus [Download the latest release](https://prometheus.io/download) of Prometheus for your platform, then extract and run it: ```bash tar xvfz prometheus-*.tar.gz cd prometheus-* ``` Before starting Prometheus, let's configure it. ## Configuring Prometheus to monitor itself Prometheus collects metrics from monitored targets by scraping metrics HTTP endpoints on these targets. Since Prometheus also exposes data in the same manner about itself, it can also scrape and monitor its own health. While a Prometheus server that collects only data about itself is not very useful in practice, it is a good starting example. Save the following basic Prometheus configuration as a file named `prometheus.yml`: ```yaml global: scrape_interval: 15s # By default, scrape targets every 15 seconds. # Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: monitor: 'codelab-monitor' # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=` to any timeseries scraped from this config. - job_name: 'prometheus' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:9090'] ``` For a complete specification of configuration options, see the [configuration documentation](configuration/configuration.md). ## Starting Prometheus To start Prometheus with your newly created configuration file, change to the directory containing the Prometheus binary and run: ```bash # Start Prometheus. # By default, Prometheus stores its database in ./data (flag --storage.tsdb.path). ./prometheus --config.file=prometheus.yml ``` Prometheus should start up. You should also be able to browse to a status page about itself at [localhost:9090](http://localhost:9090). Give it a couple of seconds to collect data about itself from its own HTTP metrics endpoint. You can also verify that Prometheus is serving metrics about itself by navigating to its metrics endpoint: [localhost:9090/metrics](http://localhost:9090/metrics) ## Using the expression browser Let us try looking at some data that Prometheus has collected about itself. To use Prometheus's built-in expression browser, navigate to http://localhost:9090/graph and choose the "Console" view within the "Graph" tab. As you can gather from [localhost:9090/metrics](http://localhost:9090/metrics), one metric that Prometheus exports about itself is called `prometheus_target_interval_length_seconds` (the actual amount of time between target scrapes). Go ahead and enter this into the expression console: ``` prometheus_target_interval_length_seconds ``` This should return a number of different time series (along with the latest value recorded for each), all with the metric name `prometheus_target_interval_length_seconds`, but with different labels. These labels designate different latency percentiles and target group intervals. If we were only interested in the 99th percentile latencies, we could use this query to retrieve that information: ``` prometheus_target_interval_length_seconds{quantile="0.99"} ``` To count the number of returned time series, you could write: ``` count(prometheus_target_interval_length_seconds) ``` For more about the expression language, see the [expression language documentation](querying/basics.md). ## Using the graphing interface To graph expressions, navigate to http://localhost:9090/graph and use the "Graph" tab. For example, enter the following expression to graph the per-second rate of chunks being created in the self-scraped Prometheus: ``` rate(prometheus_tsdb_head_chunks_created_total[1m]) ``` Experiment with the graph range parameters and other settings. ## Starting up some sample targets Let us make this more interesting and start some example targets for Prometheus to scrape. The Go client library includes an example which exports fictional RPC latencies for three services with different latency distributions. Ensure you have the [Go compiler installed](https://golang.org/doc/install) and have a [working Go build environment](https://golang.org/doc/code.html) (with correct `GOPATH`) set up. Download the Go client library for Prometheus and run three of these example processes: ```bash # Fetch the client library code and compile example. git clone https://github.com/prometheus/client_golang.git cd client_golang/examples/random go get -d go build # Start 3 example targets in separate terminals: ./random -listen-address=:8080 ./random -listen-address=:8081 ./random -listen-address=:8082 ``` You should now have example targets listening on http://localhost:8080/metrics, http://localhost:8081/metrics, and http://localhost:8082/metrics. ## Configuring Prometheus to monitor the sample targets Now we will configure Prometheus to scrape these new targets. Let's group all three endpoints into one job called `example-random`. However, imagine that the first two endpoints are production targets, while the third one represents a canary instance. To model this in Prometheus, we can add several groups of endpoints to a single job, adding extra labels to each group of targets. In this example, we will add the `group="production"` label to the first group of targets, while adding `group="canary"` to the second. To achieve this, add the following job definition to the `scrape_configs` section in your `prometheus.yml` and restart your Prometheus instance: ```yaml scrape_configs: - job_name: 'example-random' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:8080', 'localhost:8081'] labels: group: 'production' - targets: ['localhost:8082'] labels: group: 'canary' ``` Go to the expression browser and verify that Prometheus now has information about time series that these example endpoints expose, such as the `rpc_durations_seconds` metric. ## Configure rules for aggregating scraped data into new time series Though not a problem in our example, queries that aggregate over thousands of time series can get slow when computed ad-hoc. To make this more efficient, Prometheus allows you to prerecord expressions into completely new persisted time series via configured recording rules. Let's say we are interested in recording the per-second rate of example RPCs (`rpc_durations_seconds_count`) averaged over all instances (but preserving the `job` and `service` dimensions) as measured over a window of 5 minutes. We could write this as: ``` avg(rate(rpc_durations_seconds_count[5m])) by (job, service) ``` Try graphing this expression. To record the time series resulting from this expression into a new metric called `job_service:rpc_durations_seconds_count:avg_rate5m`, create a file with the following recording rule and save it as `prometheus.rules.yml`: ``` groups: - name: example rules: - record: job_service:rpc_durations_seconds_count:avg_rate5m expr: avg(rate(rpc_durations_seconds_count[5m])) by (job, service) ``` To make Prometheus pick up this new rule, add a `rule_files` statement to the `global` configuration section in your `prometheus.yml`. The config should now look like this: ```yaml global: scrape_interval: 15s # By default, scrape targets every 15 seconds. evaluation_interval: 15s # Evaluate rules every 15 seconds. # Attach these extra labels to all timeseries collected by this Prometheus instance. external_labels: monitor: 'codelab-monitor' rule_files: - 'prometheus.rules.yml' scrape_configs: - job_name: 'prometheus' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:9090'] - job_name: 'example-random' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:8080', 'localhost:8081'] labels: group: 'production' - targets: ['localhost:8082'] labels: group: 'canary' ``` Restart Prometheus with the new configuration and verify that a new time series with the metric name `job_service:rpc_durations_seconds_count:avg_rate5m` is now available by querying it through the expression browser or graphing it. prometheus-2.1.0+ds/docs/images/000077500000000000000000000000001323116307200165025ustar00rootroot00000000000000prometheus-2.1.0+ds/docs/images/remote_integrations.png000066400000000000000000000342541323116307200233010ustar00rootroot00000000000000PNG  IHDR`9Z IDATx^tTƿMHޫ4B@"%TE)"J^wҫ I'4HЫ IaeɆ99f|v{ݹi( ( (`y4DQ@DQ@DE ( (1 zRDDQ@DQ@nDQ@DQ@n=f)e"( ( r Ok-L9fv( Kuҩ*:bx<(*&( .W@q@Y4 `5 %= R`pF @ )9?/$(`+>|w yo[ J``35P k4pz' G5TO袏 /lpE\PL{y(TznF`gSLҀ^[jDQ@zó ~].h,w%."ߕ(<Ez|SXS/7~_ϲSurgV ;7}9%E'58$u <0oY(Zzg |?HbmxpK8XA{[Y3Zp*`-C#>m0 24{K-bsXKh'ChDk%sے weE< cqߕ_9Q:;xRc}T6,}[tXo#V7Q QepOC,?~[Y-qsp ,^SZFA5M[v!-0( 3 zGԈ[6Gpmd Xъ]z|Cf4LC\6P p0 h?bViauO ^S`Q79Q+` kj1ߕg!N>!f7~}ZeeM}0^7\>ӱȆU8EԫR¬M̐<[ i5j<\0q=0n`z z3=;C A#[>Do0`B"nH0m7Go53[>Jtd'W6FsS(*^]E{xXUTioi4&G^7z-CxsNfy*.[,vfXX nU(BHn/DԴ4A ! T0[=x4L L0f1=: fԮ̄@xzCͥ7pKo8-͖N񸡍Oj1-J*!ACⱍhf?ܐG\jxn/&(27\W gٔkț.agʊ/dXL5|&, \(Xe-H#fE8>@zdc<܌ÊX]B•x#ne߳݅91$nY>q5ls*&2{K*7276΢8Yf •e6,\ܜp&zyNDv@`dX*1?¬FyXX/Ką}e$Mb5( ߕ1Z:[2w%C \! i"+هeqxMeMwJh'|r"cΖZL$X,}2-4Q@ˁ?@+cOXz[wW)杼*% G֜k4n&t @U,St#d_ } :Kf#pk҅qYL= ܚi5\dn=QLb6"o6sm|Y`x`eR:17p#3u&GU L-hh52[Iknͳb[(?mr z9l(s5nY<3/³l(w)pu [wZhںhڍ ~4ODQi5'ˁn=rp_` Z:Ma2,ΜJEbZ]Һ aQ~g,~!e\ @~%K`U%p3NM֌dsdA;OW@<Žp PXg  ,=Qt\^_pV!t  L-h  %ț/ T[6ncMj`Lbn=6[zFX'LoWI%ׯXVƗ@{h P#:nxFo%zLo?ۨ]rtAK$Gɖ`}Kn8E[A! ӯ3\9I^ mD1-ٺ(KKXB_zDx`eR@:KIwǽ$#ZuHw6XַvK =a6b{.'$]8sNx;-Gtn냡L;~L̒Λ9E ahۥ'>-D' ,Ykں1BBCq ֮}s;up,3 EJ0epts0MZvS'zb,߸<7^WFxLEtn[GIRog%E cyLX\Xn=viNEB3hN51q)69-/-zׯ]Ab9qt}/X0k*o<_pfM9K74iRd˙G> x|zٷo$-T'PuZs32%nm ƪ2+ pp )`[iPpo a F 疰9{F̝OYʚ kv f衧 ۩Oذm(IsϞ:#!yx{{sV·{ nvdL unNCf&p7* z7z8zf[ e[U\+Yz>4$$Ӹqn^A;*$a9izq=:6Ú𑿟MZ*ȣ ؀[c<{}?-smg+bʃW-v} e?˂tS#{LJsU[a¼yߡ=ab`m ēSEn]3烐.M*{|o/_CoGUԏ4K{Qy>/Jʈkv8׵.E&%L7 kwW1F0bL˛soQmש0ߖ m 1(V?uǁ;9k 9%NXTƃG̓7[6,y8reθi;tB;%0iB?NF¬dMֽO7ې3[&(9|e>]Zwf^X-⩬}cn\xaAO[*8chL59rE칰ar3?˘-X`i; LL툁|dʒ AÇعm]}b?Ю[/o{Ia π%+Nn ƎV ww=Q5x[Lij C|y5wȩHk^ ^{q|dɰfv/T~ /VrHi0پ/^D\Pڷ(=~_.CѮA4U;|wWoDJ"Cf7 ~S8|.dIq>r~Fʔ`_+wQDqxłZ69Df۫o!c*T[NEb,&ݹ/,_>P\(ܪ4B[vĩsȜ1W(oo/ܾ{6@b5;tKFc#:gH[Ns8T)㣪 ~)ZP-s6FB8D66T1XJy|XRf[g 7nO;>C'H25$dE&96 =z$I8vmjru>ӣYS~XrhzH2%ȮZ2/Kq ܺV_=(`v>q&3%5AZ?Z˔_wזucs]<Z[sSP[=y5j!ܯn gF[p#\:G}Frbi {͒>=V}!۱&OAF 1s'd?-cڵG8{)k}j|?RLJXmaB??aKqrro4f+b/?`έ]9]3(sm&τ|}mC}(׬Ǐ#5T/*++Օf[zb 6Wn(aBMadhSLͻUp۳CS<ٳd@jA7m>،KWB)!+^&jW+Pg~6CR:Z~1;ƻ5)}Oym/Htk7m}&9S':8-3+p9EWzgNE;Ϙv'pks(j{cz~B 3f¦i, 2ǎ{.5{H*%v̚uͤ}$Gvm&6}9 bڵ R@o`ܺ{f+7n &NP6N1c@?sa?+6oqoe7g2k([ ]4_Avmu{Qp{)*_V!*]4e 0/;m\y]n]_0+ܮټ>m!w@ׯŒ L 6n@w|'hѭ&\bpj6~}KԇMNu[vGHH = ֫Y vfJXH*@s8[?&{+8N0]O/s-:r4oCq%_ ]{\t5?n`MfEmLOM+*cAt=Yf?X,Ϻgݻ/hGo#E X3)b% "+`V:ݸuG\4s1ص: aaV JiH'Ӆmq_Mm%RPs\pӶt,<~2*c@n!7kJrMk,@ fc3c{y˔]TT_*@W_SSi}~}dw*beWF,c.=֥߭Jxp2XU6L+I9:^YJ-ef_3x-hed͞Sqsvءah͗(X͕'?>o{wohҲ#Ξ:+cH2!w֍=BoO*-,k>i4j]l@ۘk(=T >-&@-U+6Fx% ƛᖐr f*oln駅~2dDv`]Uj W-3 ƴyЮY]JFe;~$MrQ+c~Y4iҡ|qSǏPOjz ܸIgIDATyb͟cԧ1flf\'$pdAx@|xfBءغy-fO: S kj-mLưvMbÞS n-U R=ucGCƁ0uJUcذuÔ fyv@.pilãja^/]&p<-/gvs&$}>>cFtivh~ڸ:6pV|+|P-ct l g58Buɰi  y?߷pcfnͰ .AKF[_j[_`Ɵ~6O7W/Ǭ%绎H*5t_ U|.оΝ[;t p[,oj]9söТQMu+,VT͟9=RpiJc&CBȾ=TRӴ׋iyn_0 [>i(P[8n=|n^ﺴ񣇐!cfܺygDa5{.}z86K8IRL˕=T ^{wxC Ο=3=o8x޳}M+րN `H' %]G//g)`۸XV?T)*TR~MW-6kezj 5no0mX,P@O[-kM}Q_zfW.]@PVl٫-plEM؟ۯ$?&RL̯|P}[*FKh' ܺEfkGmN 75ق8[g+jnM4N1L)25uU,lx;ZmVn{dKpۥM:[Q'pkڥqaNܝH[ӬO?hb^4d>iib48>vM0 &ܺP\t+-0 XX X@*Ob `SkGr/'pK-pfB^2X_@Nd#qnp{6(`?좘Gq1j0q~D}%9Ym)8sV( 91(0P(7(K^'ܺzZ*  aVA xY:'.;Sc\h8~l XF !2r.BnsL[Kx#+kOBl* `~n+G!9@62k?8s4m `v0ejE_ȮǟPg$v_yICte5( ߕ..1IΖ .4 CWB;YΖ]U`GI'*` ط8aٍ@YW]ړNI{ >pB-="B)! k5;KΨPn UYç@ C'wγ^z8;K  v<[]}nS}x@ hl9@oh `k!\Bm# ر FMO-OG<2k'<ң͛̐ dm|=<8oz`7^ h_?f/ ߕ/mjJUS ?EC8,ZbZ [Y <)bFX#lcFO+Tͧԏy#F#:^湥ǒA^/B"[H#[Kn#; zENo#IWI;Zni= l1 `(0Nh|No67Ԟa>獂=ѫ`}."hBpNm Sa4Εk¹iIAC} Mh}n ">Ux3%[{#6{ߕ,ߕ̽|.t/%⁵,z"qmI띜 {8,>I X#@gό°3ƕGFWpA'=1An#bhZ ˄7zzGc'a?ƊQ?(?|s ^fnjl3PM9gzNhضo(}BF]j%q|1c^FFzO ޶=71ksgl#- 54~ɸ9 2~bǖ7&nVGYv2™K}W~W g 눼vVp{CTq 'xTJ+=r3ś%zHPd'uM = S ٥wMB$=1; ]yBq|PD"8{Y8z?K4xa-h{=/z9yyfK: F.pxf1:jM]kuz֯xm"Xp^g ҃ny#HKGpss!=Fݑj/#6xQ3DYmII"ߕQϬ'XG-Y]]i֙kH*YWAPcoeVp&p]!=uB !Oz7g8O>&r:a^Un"1!`z*i&'*x 7b齺Yn^"1^<>ZjХǷ_" $1数Lh]@2Ƙ1@F371_,aZӣn遦A9<@wp!f7HH 5fFt?ܔG-7}6PpͨGDpGZ [N5RKtqm7 rFƍ(&Q'HeIˤkfj3\7N)E"c Q0Hh25ƀun&'ny S[1d>Uq>'> nbb(z&tS861cF%nz<޸1Xj8g[_xsNo9Av "'"-Gx&:ahmF:5Gvp]#7P1n p^Săc)zl-aMeY-Fր+cň Aaa֦ز/'G,b`' Pg͉Y}K?s3W15IY腍B&8YIp Et&C||BgY6r?ˏ'P[_[MEanx{XgG ZpG}qQVW̚ߙрFIWmZc5YO&4n;,]2cڱC"[}Wtr~go//,GAX?Ԋ{aaGO{m+( ( 1T@6颀( ( Q@'$L4<Ʊl{2$Ϭv Ș`ُ(!Rz .GrTnyqV-ulp11 0vd\-A7-A8ߝkUDQuC1||O/&11 EhCyM= HHfjCgK-G\F.ϋh\4>m  h!zC*e8 ohC'aܜ oz9kS%|rC^xfq37= [̈́mzNAmd}AO0CFр[c<5o2YҜ3ohT( (k ƚ7=rSH0&&zJg@ ^[CpQm(cl"~P WFs4nD6Q1a4i;cH^Gp ee3j27E4Nt. HO1714 i`z tL,[P0n!Mmdo.-  \Oji( @) pkRǛ7t_n폡gP/#J};Ӡٯ1i73#_UxdcscS4Dvh ( * p+(  pLDQ@DT@-M\7qJDQ@R[\61ZDQ@DQ <nDQ@DQ@n=f)e"( ( r ( ( x2Q@DQ@D[DQ@DQ@ 0.1 FOR 5m ANNOTATIONS { summary = "High frontend request latency", } ``` Would look like this: ```yaml groups: - name: example.rules rules: - record: job:request_duration_seconds:histogram_quantile99 expr: histogram_quantile(0.99, sum(rate(request_duration_seconds_bucket[1m])) BY (le, job)) - alert: FrontendRequestLatency expr: job:request_duration_seconds:histogram_quantile99{job="frontend"} > 0.1 for: 5m annotations: summary: High frontend request latency ``` To help with the change, the `promtool` tool has a mode to automate the rules conversion. Given a `.rules` file, it will output a `.rules.yml` file in the new format. For example: ``` $ promtool update rules example.rules ``` Note that you will need to use promtool from 2.0, not 1.8. ## Storage The data format in Prometheus 2.0 has completely changed and is not backwards compatible with 1.8. To retain access to your historic monitoring data we recommend you run a non-scraping Prometheus instance running at least version 1.8.1 in parallel with your Prometheus 2.0 instance, and have the new server read existing data from the old one via the remote read protocol. Your Prometheus 1.8 instance should be started with the following flags and an config file containing only the `external_labels` setting (if any): ``` $ ./prometheus-1.8.1.linux-amd64/prometheus -web.listen-address ":9094" -config.file old.yml ``` Prometheus 2.0 can then be started (on the same machine) with the following flags: ``` $ ./prometheus-2.0.0.linux-amd64/prometheus --config.file prometheus.yml ``` Where `prometheus.yml` contains in addition to your full existing configuration, the stanza: ```yaml remote_read: - url: "http://localhost:9094/api/v1/read" ``` ## PromQL The following features have been removed from PromQL: - `drop_common_labels` function - the `without` aggregation modifier should be used instead. - `keep_common` aggregation modifier - the `by` modifier should be used instead. - `count_scalar` function - use cases are better handled by `absent()` or correct propagation of labels in operations. See [issue #3060](https://github.com/prometheus/prometheus/issues/3060) for more details. ## Miscellaneous ### Prometheus non-root user The Prometheus Docker image is now built to [run Prometheus as a non-root user](https://github.com/prometheus/prometheus/pull/2859). If you want the Prometheus UI/API to listen on a low port number (say, port 80), you'll need to override it. For Kubernetes, you would use the following YAML: ```yaml apiVersion: v1 kind: Pod metadata: name: security-context-demo-2 spec: securityContext: runAsUser: 0 ... ``` See [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for more details. If you're using Docker, then the following snippet would be used: ``` docker run -u root -p 80:80 prom/prometheus:v2.0.0-rc.2 --web.listen-address :80 ``` ### Prometheus lifecycle If you use the Prometheus `/-/reload` HTTP endpoint to [automatically reload your Prometheus config when it changes](configuration/configuration.md), these endpoints are disabled by default for security reasons in Prometheus 2.0. To enable them, set the `--web.enable-lifecycle` flag. prometheus-2.1.0+ds/docs/querying/000077500000000000000000000000001323116307200171005ustar00rootroot00000000000000prometheus-2.1.0+ds/docs/querying/api.md000066400000000000000000000273421323116307200202030ustar00rootroot00000000000000--- title: HTTP API sort_rank: 7 --- # HTTP API The current stable HTTP API is reachable under `/api/v1` on a Prometheus server. Any non-breaking additions will be added under that endpoint. ## Format overview The API response format is JSON. Every successful API request returns a `2xx` status code. Invalid requests that reach the API handlers return a JSON error object and one of the following HTTP response codes: - `400 Bad Request` when parameters are missing or incorrect. - `422 Unprocessable Entity` when an expression can't be executed ([RFC4918](http://tools.ietf.org/html/rfc4918#page-78)). - `503 Service Unavailable` when queries time out or abort. Other non-`2xx` codes may be returned for errors occurring before the API endpoint is reached. The JSON response envelope format is as follows: ``` { "status": "success" | "error", "data": , // Only set if status is "error". The data field may still hold // additional data. "errorType": "", "error": "" } ``` Input timestamps may be provided either in [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) format or as a Unix timestamp in seconds, with optional decimal places for sub-second precision. Output timestamps are always represented as Unix timestamps in seconds. Names of query parameters that may be repeated end with `[]`. `` placeholders refer to Prometheus [time series selectors](basics.md#time-series-selectors) like `http_requests_total` or `http_requests_total{method=~"(GET|POST)"}` and need to be URL-encoded. `` placeholders refer to Prometheus duration strings of the form `[0-9]+[smhdwy]`. For example, `5m` refers to a duration of 5 minutes. ## Expression queries Query language expressions may be evaluated at a single instant or over a range of time. The sections below describe the API endpoints for each type of expression query. ### Instant queries The following endpoint evaluates an instant query at a single point in time: ``` GET /api/v1/query ``` URL query parameters: - `query=`: Prometheus expression query string. - `time=`: Evaluation timestamp. Optional. - `timeout=`: Evaluation timeout. Optional. Defaults to and is capped by the value of the `-query.timeout` flag. The current server time is used if the `time` parameter is omitted. The `data` section of the query result has the following format: ``` { "resultType": "matrix" | "vector" | "scalar" | "string", "result": } ``` `` refers to the query result data, which has varying formats depending on the `resultType`. See the [expression query result formats](#expression-query-result-formats). The following example evaluates the expression `up` at the time `2015-07-01T20:10:51.781Z`: ```json $ curl 'http://localhost:9090/api/v1/query?query=up&time=2015-07-01T20:10:51.781Z' { "status" : "success", "data" : { "resultType" : "vector", "result" : [ { "metric" : { "__name__" : "up", "job" : "prometheus", "instance" : "localhost:9090" }, "value": [ 1435781451.781, "1" ] }, { "metric" : { "__name__" : "up", "job" : "node", "instance" : "localhost:9100" }, "value" : [ 1435781451.781, "0" ] } ] } } ``` ### Range queries The following endpoint evaluates an expression query over a range of time: ``` GET /api/v1/query_range ``` URL query parameters: - `query=`: Prometheus expression query string. - `start=`: Start timestamp. - `end=`: End timestamp. - `step=`: Query resolution step width. - `timeout=`: Evaluation timeout. Optional. Defaults to and is capped by the value of the `-query.timeout` flag. The `data` section of the query result has the following format: ``` { "resultType": "matrix", "result": } ``` For the format of the `` placeholder, see the [range-vector result format](#range-vectors). The following example evaluates the expression `up` over a 30-second range with a query resolution of 15 seconds. ```json $ curl 'http://localhost:9090/api/v1/query_range?query=up&start=2015-07-01T20:10:30.781Z&end=2015-07-01T20:11:00.781Z&step=15s' { "status" : "success", "data" : { "resultType" : "matrix", "result" : [ { "metric" : { "__name__" : "up", "job" : "prometheus", "instance" : "localhost:9090" }, "values" : [ [ 1435781430.781, "1" ], [ 1435781445.781, "1" ], [ 1435781460.781, "1" ] ] }, { "metric" : { "__name__" : "up", "job" : "node", "instance" : "localhost:9091" }, "values" : [ [ 1435781430.781, "0" ], [ 1435781445.781, "0" ], [ 1435781460.781, "1" ] ] } ] } } ``` ## Querying metadata ### Finding series by label matchers The following endpoint returns the list of time series that match a certain label set. ``` GET /api/v1/series ``` URL query parameters: - `match[]=`: Repeated series selector argument that selects the series to return. At least one `match[]` argument must be provided. - `start=`: Start timestamp. - `end=`: End timestamp. The `data` section of the query result consists of a list of objects that contain the label name/value pairs which identify each series. The following example returns all series that match either of the selectors `up` or `process_start_time_seconds{job="prometheus"}`: ```json $ curl -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}' { "status" : "success", "data" : [ { "__name__" : "up", "job" : "prometheus", "instance" : "localhost:9090" }, { "__name__" : "up", "job" : "node", "instance" : "localhost:9091" }, { "__name__" : "process_start_time_seconds", "job" : "prometheus", "instance" : "localhost:9090" } ] } ``` ### Querying label values The following endpoint returns a list of label values for a provided label name: ``` GET /api/v1/label//values ``` The `data` section of the JSON response is a list of string label names. This example queries for all label values for the `job` label: ```json $ curl http://localhost:9090/api/v1/label/job/values { "status" : "success", "data" : [ "node", "prometheus" ] } ``` ## Expression query result formats Expression queries may return the following response values in the `result` property of the `data` section. `` placeholders are numeric sample values. JSON does not support special float values such as `NaN`, `Inf`, and `-Inf`, so sample values are transferred as quoted JSON strings rather than raw numbers. ### Range vectors Range vectors are returned as result type `matrix`. The corresponding `result` property has the following format: ``` [ { "metric": { "": "", ... }, "values": [ [ , "" ], ... ] }, ... ] ``` ### Instant vectors Instant vectors are returned as result type `vector`. The corresponding `result` property has the following format: ``` [ { "metric": { "": "", ... }, "value": [ , "" ] }, ... ] ``` ### Scalars Scalar results are returned as result type `scalar`. The corresponding `result` property has the following format: ``` [ , "" ] ``` ### Strings String results are returned as result type `string`. The corresponding `result` property has the following format: ``` [ , "" ] ``` ## Targets > This API is experimental as it is intended to be extended with targets > dropped due to relabelling in the future. The following endpoint returns an overview of the current state of the Prometheus target discovery: ``` GET /api/v1/targets ``` Currently only the active targets are part of the response. ```json $ curl http://localhost:9090/api/v1/targets { "status": "success", [3/11] "data": { "activeTargets": [ { "discoveredLabels": { "__address__": "127.0.0.1:9090", "__metrics_path__": "/metrics", "__scheme__": "http", "job": "prometheus" }, "labels": { "instance": "127.0.0.1:9090", "job": "prometheus" }, "scrapeUrl": "http://127.0.0.1:9090/metrics", "lastError": "", "lastScrape": "2017-01-17T15:07:44.723715405+01:00", "health": "up" } ] } } ``` ## Alertmanagers > This API is experimental as it is intended to be extended with Alertmanagers > dropped due to relabelling in the future. The following endpoint returns an overview of the current state of the Prometheus alertmanager discovery: ``` GET /api/v1/alertmanagers ``` Currently only the active Alertmanagers are part of the response. ```json $ curl http://localhost:9090/api/v1/alertmanagers { "status": "success", "data": { "activeAlertmanagers": [ { "url": "http://127.0.0.1:9090/api/v1/alerts" } ] } } ``` ## TSDB Admin APIs These are APIs that expose database functionalities for the advanced user. These APIs are not enabled unless the `--web.enable-admin-api` is set. We also expose a gRPC API whose definition can be found [here](https://github.com/prometheus/prometheus/blob/master/prompb/rpc.proto). This is experimental and might change in the future. ### Snapshot Snapshot creates a snapshot of all current data into `snapshots/-` under the TSDB's data directory and returns the directory as response. ``` POST /api/v1/admin/tsdb/snapshot ``` ```json $ curl -XPOST http://localhost:9090/api/v1/admin/tsdb/snapshot { "status": "success", "data": { "name": "20171210T211224Z-2be650b6d019eb54" } } ``` The snapshot now exists at `/snapshots/20171210T211224Z-2be650b6d019eb54` *New in v2.1* ### Delete Series DeleteSeries deletes data for a selection of series in a time range. The actual data still exists on disk and is cleaned up in future compactions or can be explicitly cleaned up by hitting the Clean Tombstones endpoint. If successful, a `204` is returned. ``` POST /api/v1/admin/tsdb/delete_series ``` URL query parameters: - `match[]=`: Repeated label matcher argument that selects the series to delete. At least one `match[]` argument must be provided. - `start=`: Start timestamp. Optional and defaults to minimum possible time. - `end=`: End timestamp. Optional and defaults to maximum possible time. Not mentioning both start and end times would clear all the data for the matched series in the database. Example: ```json $ curl -X DELETE \ -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}' ``` *New in v2.1* ### Clean Tombstones CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. This can be used after deleting series to free up space. If successful, a `204` is returned. ``` POST /api/v1/admin/tsdb/clean_tombstones ``` This takes no parameters or body. ```json $ curl -XPOST http://localhost:9090/api/v1/admin/tsdb/clean_tombstones ``` *New in v2.1* prometheus-2.1.0+ds/docs/querying/basics.md000066400000000000000000000214531323116307200206730ustar00rootroot00000000000000--- title: Querying basics nav_title: Basics sort_rank: 1 --- # Querying Prometheus Prometheus provides a functional expression language that lets the user select and aggregate time series data in real time. The result of an expression can either be shown as a graph, viewed as tabular data in Prometheus's expression browser, or consumed by external systems via the [HTTP API](api.md). ## Examples This document is meant as a reference. For learning, it might be easier to start with a couple of [examples](examples.md). ## Expression language data types In Prometheus's expression language, an expression or sub-expression can evaluate to one of four types: * **Instant vector** - a set of time series containing a single sample for each time series, all sharing the same timestamp * **Range vector** - a set of time series containing a range of data points over time for each time series * **Scalar** - a simple numeric floating point value * **String** - a simple string value; currently unused Depending on the use-case (e.g. when graphing vs. displaying the output of an expression), only some of these types are legal as the result from a user-specified expression. For example, an expression that returns an instant vector is the only type that can be directly graphed. ## Literals ### String literals Strings may be specified as literals in single quotes, double quotes or backticks. PromQL follows the same [escaping rules as Go](https://golang.org/ref/spec#String_literals). In single or double quotes a backslash begins an escape sequence, which may be followed by `a`, `b`, `f`, `n`, `r`, `t`, `v` or `\`. Specific characters can be provided using octal (`\nnn`) or hexadecimal (`\xnn`, `\unnnn` and `\Unnnnnnnn`). No escaping is processed inside backticks. Unlike Go, Prometheus does not discard newlines inside backticks. Example: "this is a string" 'these are unescaped: \n \\ \t' `these are not unescaped: \n ' " \t` ### Float literals Scalar float values can be literally written as numbers of the form `[-](digits)[.(digits)]`. -2.43 ## Time series Selectors ### Instant vector selectors Instant vector selectors allow the selection of a set of time series and a single sample value for each at a given timestamp (instant): in the simplest form, only a metric name is specified. This results in an instant vector containing elements for all time series that have this metric name. This example selects all time series that have the `http_requests_total` metric name: http_requests_total It is possible to filter these time series further by appending a set of labels to match in curly braces (`{}`). This example selects only those time series with the `http_requests_total` metric name that also have the `job` label set to `prometheus` and their `group` label set to `canary`: http_requests_total{job="prometheus",group="canary"} It is also possible to negatively match a label value, or to match label values against regular expressions. The following label matching operators exist: * `=`: Select labels that are exactly equal to the provided string. * `!=`: Select labels that are not equal to the provided string. * `=~`: Select labels that regex-match the provided string (or substring). * `!~`: Select labels that do not regex-match the provided string (or substring). For example, this selects all `http_requests_total` time series for `staging`, `testing`, and `development` environments and HTTP methods other than `GET`. http_requests_total{environment=~"staging|testing|development",method!="GET"} Label matchers that match empty label values also select all time series that do not have the specific label set at all. Regex-matches are fully anchored. Vector selectors must either specify a name or at least one label matcher that does not match the empty string. The following expression is illegal: {job=~".*"} # Bad! In contrast, these expressions are valid as they both have a selector that does not match empty label values. {job=~".+"} # Good! {job=~".*",method="get"} # Good! Label matchers can also be applied to metric names by matching against the internal `__name__` label. For example, the expression `http_requests_total` is equivalent to `{__name__="http_requests_total"}`. Matchers other than `=` (`!=`, `=~`, `!~`) may also be used. The following expression selects all metrics that have a name starting with `job:`: {__name__=~"job:.*"} All regular expressions in Prometheus use [RE2 syntax](https://github.com/google/re2/wiki/Syntax). ### Range Vector Selectors Range vector literals work like instant vector literals, except that they select a range of samples back from the current instant. Syntactically, a range duration is appended in square brackets (`[]`) at the end of a vector selector to specify how far back in time values should be fetched for each resulting range vector element. Time durations are specified as a number, followed immediately by one of the following units: * `s` - seconds * `m` - minutes * `h` - hours * `d` - days * `w` - weeks * `y` - years In this example, we select all the values we have recorded within the last 5 minutes for all time series that have the metric name `http_requests_total` and a `job` label set to `prometheus`: http_requests_total{job="prometheus"}[5m] ### Offset modifier The `offset` modifier allows changing the time offset for individual instant and range vectors in a query. For example, the following expression returns the value of `http_requests_total` 5 minutes in the past relative to the current query evaluation time: http_requests_total offset 5m Note that the `offset` modifier always needs to follow the selector immediately, i.e. the following would be correct: sum(http_requests_total{method="GET"} offset 5m) // GOOD. While the following would be *incorrect*: sum(http_requests_total{method="GET"}) offset 5m // INVALID. The same works for range vectors. This returns the 5-minutes rate that `http_requests_total` had a week ago: rate(http_requests_total[5m] offset 1w) ## Operators Prometheus supports many binary and aggregation operators. These are described in detail in the [expression language operators](operators.md) page. ## Functions Prometheus supports several functions to operate on data. These are described in detail in the [expression language functions](functions.md) page. ## Gotchas ### Staleness When queries are run, timestamps at which to sample data are selected independently of the actual present time series data. This is mainly to support cases like aggregation (`sum`, `avg`, and so on), where multiple aggregated time series do not exactly align in time. Because of their independence, Prometheus needs to assign a value at those timestamps for each relevant time series. It does so by simply taking the newest sample before this timestamp. If a target scrape or rule evaluation no longer returns a sample for a time series that was previously present, that time series will be marked as stale. If a target is removed, its previously returned time series will be marked as stale soon afterwards. If a query is evaluated at a sampling timestamp after a time series is marked stale, then no value is returned for that time series. If new samples are subsequently ingested for that time series, they will be returned as normal. If no sample is found (by default) 5 minutes before a sampling timestamp, no value is returned for that time series at this point in time. This effectively means that time series "disappear" from graphs at times where their latest collected sample is older than 5 minutes or after they are marked stale. Staleness will not be marked for time series that have timestamps included in their scrapes. Only the 5 minute threshold will be applied in that case. ### Avoiding slow queries and overloads If a query needs to operate on a very large amount of data, graphing it might time out or overload the server or browser. Thus, when constructing queries over unknown data, always start building the query in the tabular view of Prometheus's expression browser until the result set seems reasonable (hundreds, not thousands, of time series at most). Only when you have filtered or aggregated your data sufficiently, switch to graph mode. If the expression still takes too long to graph ad-hoc, pre-record it via a [recording rule](../configuration/recording_rules.md#recording-rules). This is especially relevant for Prometheus's query language, where a bare metric name selector like `api_http_requests_total` could expand to thousands of time series with different labels. Also keep in mind that expressions which aggregate over many time series will generate load on the server even if the output is only a small number of time series. This is similar to how it would be slow to sum all values of a column in a relational database, even if the output value is only a single number. prometheus-2.1.0+ds/docs/querying/examples.md000066400000000000000000000062071323116307200212450ustar00rootroot00000000000000--- title: Querying examples nav_title: Examples sort_rank: 4 --- # Query examples ## Simple time series selection Return all time series with the metric `http_requests_total`: http_requests_total Return all time series with the metric `http_requests_total` and the given `job` and `handler` labels: http_requests_total{job="apiserver", handler="/api/comments"} Return a whole range of time (in this case 5 minutes) for the same vector, making it a range vector: http_requests_total{job="apiserver", handler="/api/comments"}[5m] Note that an expression resulting in a range vector cannot be graphed directly, but viewed in the tabular ("Console") view of the expression browser. Using regular expressions, you could select time series only for jobs whose name match a certain pattern, in this case, all jobs that end with `server`. Note that this does a substring match, not a full string match: http_requests_total{job=~".*server"} All regular expressions in Prometheus use [RE2 syntax](https://github.com/google/re2/wiki/Syntax). To select all HTTP status codes except 4xx ones, you could run: http_requests_total{status!~"4.."} ## Using functions, operators, etc. Return the per-second rate for all time series with the `http_requests_total` metric name, as measured over the last 5 minutes: rate(http_requests_total[5m]) Assuming that the `http_requests_total` time series all have the labels `job` (fanout by job name) and `instance` (fanout by instance of the job), we might want to sum over the rate of all instances, so we get fewer output time series, but still preserve the `job` dimension: sum(rate(http_requests_total[5m])) by (job) If we have two different metrics with the same dimensional labels, we can apply binary operators to them and elements on both sides with the same label set will get matched and propagated to the output. For example, this expression returns the unused memory in MiB for every instance (on a fictional cluster scheduler exposing these metrics about the instances it runs): (instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024 The same expression, but summed by application, could be written like this: sum( instance_memory_limit_bytes - instance_memory_usage_bytes ) by (app, proc) / 1024 / 1024 If the same fictional cluster scheduler exposed CPU usage metrics like the following for every instance: instance_cpu_time_ns{app="lion", proc="web", rev="34d0f99", env="prod", job="cluster-manager"} instance_cpu_time_ns{app="elephant", proc="worker", rev="34d0f99", env="prod", job="cluster-manager"} instance_cpu_time_ns{app="turtle", proc="api", rev="4d3a513", env="prod", job="cluster-manager"} instance_cpu_time_ns{app="fox", proc="widget", rev="4d3a513", env="prod", job="cluster-manager"} ... ...we could get the top 3 CPU users grouped by application (`app`) and process type (`proc`) like this: topk(3, sum(rate(instance_cpu_time_ns[5m])) by (app, proc)) Assuming this metric contains one time series per running instance, you could count the number of running instances per application like this: count(instance_cpu_time_ns) by (app) prometheus-2.1.0+ds/docs/querying/functions.md000066400000000000000000000347121323116307200214410ustar00rootroot00000000000000--- title: Query functions nav_title: Functions sort_rank: 3 --- # Functions Some functions have default arguments, e.g. `year(v=vector(time()) instant-vector)`. This means that there is one argument `v` which is an instant vector, which if not provided it will default to the value of the expression `vector(time())`. ## `abs()` `abs(v instant-vector)` returns the input vector with all sample values converted to their absolute value. ## `absent()` `absent(v instant-vector)` returns an empty vector if the vector passed to it has any elements and a 1-element vector with the value 1 if the vector passed to it has no elements. This is useful for alerting on when no time series exist for a given metric name and label combination. ``` absent(nonexistent{job="myjob"}) # => {job="myjob"} absent(nonexistent{job="myjob",instance=~".*"}) # => {job="myjob"} absent(sum(nonexistent{job="myjob"})) # => {} ``` In the second example, `absent()` tries to be smart about deriving labels of the 1-element output vector from the input vector. ## `ceil()` `ceil(v instant-vector)` rounds the sample values of all elements in `v` up to the nearest integer. ## `changes()` For each input time series, `changes(v range-vector)` returns the number of times its value has changed within the provided time range as an instant vector. ## `clamp_max()` `clamp_max(v instant-vector, max scalar)` clamps the sample values of all elements in `v` to have an upper limit of `max`. ## `clamp_min()` `clamp_min(v instant-vector, min scalar)` clamps the sample values of all elements in `v` to have a lower limit of `min`. ## `day_of_month()` `day_of_month(v=vector(time()) instant-vector)` returns the day of the month for each of the given times in UTC. Returned values are from 1 to 31. ## `day_of_week()` `day_of_week(v=vector(time()) instant-vector)` returns the day of the week for each of the given times in UTC. Returned values are from 0 to 6, where 0 means Sunday etc. ## `days_in_month()` `days_in_month(v=vector(time()) instant-vector)` returns number of days in the month for each of the given times in UTC. Returned values are from 28 to 31. ## `delta()` `delta(v range-vector)` calculates the difference between the first and last value of each time series element in a range vector `v`, returning an instant vector with the given deltas and equivalent labels. The delta is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if the sample values are all integers. The following example expression returns the difference in CPU temperature between now and 2 hours ago: ``` delta(cpu_temp_celsius{host="zeus"}[2h]) ``` `delta` should only be used with gauges. ## `deriv()` `deriv(v range-vector)` calculates the per-second derivative of the time series in a range vector `v`, using [simple linear regression](http://en.wikipedia.org/wiki/Simple_linear_regression). `deriv` should only be used with gauges. ## `exp()` `exp(v instant-vector)` calculates the exponential function for all elements in `v`. Special cases are: * `Exp(+Inf) = +Inf` * `Exp(NaN) = NaN` ## `floor()` `floor(v instant-vector)` rounds the sample values of all elements in `v` down to the nearest integer. ## `histogram_quantile()` `histogram_quantile(φ float, b instant-vector)` calculates the φ-quantile (0 ≤ φ ≤ 1) from the buckets `b` of a [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram). (See [histograms and summaries](https://prometheus.io/docs/practices/histograms) for a detailed explanation of φ-quantiles and the usage of the histogram metric type in general.) The samples in `b` are the counts of observations in each bucket. Each sample must have a label `le` where the label value denotes the inclusive upper bound of the bucket. (Samples without such a label are silently ignored.) The [histogram metric type](https://prometheus.io/docs/concepts/metric_types/#histogram) automatically provides time series with the `_bucket` suffix and the appropriate labels. Use the `rate()` function to specify the time window for the quantile calculation. Example: A histogram metric is called `http_request_duration_seconds`. To calculate the 90th percentile of request durations over the last 10m, use the following expression: histogram_quantile(0.9, rate(http_request_duration_seconds_bucket[10m])) The quantile is calculated for each label combination in `http_request_duration_seconds`. To aggregate, use the `sum()` aggregator around the `rate()` function. Since the `le` label is required by `histogram_quantile()`, it has to be included in the `by` clause. The following expression aggregates the 90th percentile by `job`: histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (job, le)) To aggregate everything, specify only the `le` label: histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (le)) The `histogram_quantile()` function interpolates quantile values by assuming a linear distribution within a bucket. The highest bucket must have an upper bound of `+Inf`. (Otherwise, `NaN` is returned.) If a quantile is located in the highest bucket, the upper bound of the second highest bucket is returned. A lower limit of the lowest bucket is assumed to be 0 if the upper bound of that bucket is greater than 0. In that case, the usual linear interpolation is applied within that bucket. Otherwise, the upper bound of the lowest bucket is returned for quantiles located in the lowest bucket. If `b` contains fewer than two buckets, `NaN` is returned. For φ < 0, `-Inf` is returned. For φ > 1, `+Inf` is returned. ## `holt_winters()` `holt_winters(v range-vector, sf scalar, tf scalar)` produces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1. `holt_winters` should only be used with gauges. ## `hour()` `hour(v=vector(time()) instant-vector)` returns the hour of the day for each of the given times in UTC. Returned values are from 0 to 23. ## `idelta()` `idelta(v range-vector)` `idelta(v range-vector)` calculates the difference between the last two samples in the range vector `v`, returning an instant vector with the given deltas and equivalent labels. `idelta` should only be used with gauges. ## `increase()` `increase(v range-vector)` calculates the increase in the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments. The following example expression returns the number of HTTP requests as measured over the last 5 minutes, per time series in the range vector: ``` increase(http_requests_total{job="api-server"}[5m]) ``` `increase` should only be used with counters. It is syntactic sugar for `rate(v)` multiplied by the number of seconds under the specified time range window, and should be used primarily for human readability. Use `rate` in recording rules so that increases are tracked consistently on a per-second basis. ## `irate()` `irate(v range-vector)` calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The following example expression returns the per-second rate of HTTP requests looking up to 5 minutes back for the two most recent data points, per time series in the range vector: ``` irate(http_requests_total{job="api-server"}[5m]) ``` `irate` should only be used when graphing volatile, fast-moving counters. Use `rate` for alerts and slow-moving counters, as brief changes in the rate can reset the `FOR` clause and graphs consisting entirely of rare spikes are hard to read. Note that when combining `irate()` with an [aggregation operator](operators.md#aggregation-operators) (e.g. `sum()`) or a function aggregating over time (any function ending in `_over_time`), always take a `irate()` first, then aggregate. Otherwise `irate()` cannot detect counter resets when your target restarts. ## `label_join()` For each timeseries in `v`, `label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)` joins all the values of all the `src_labels` using `separator` and returns the timeseries with the label `dst_label` containing the joined value. There can be any number of `src_labels` in this function. This example will return a vector with each time series having a `foo` label with the value `a,b,c` added to it: ``` label_join(up{job="api-server",src1="a",src2="b",src3="c"}, "foo", ",", "src1", "src2", "src3") ``` ## `label_replace()` For each timeseries in `v`, `label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)` matches the regular expression `regex` against the label `src_label`. If it matches, then the timeseries is returned with the label `dst_label` replaced by the expansion of `replacement`. `$1` is replaced with the first matching subgroup, `$2` with the second etc. If the regular expression doesn't match then the timeseries is returned unchanged. This example will return a vector with each time series having a `foo` label with the value `a` added to it: ``` label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*") ``` ## `ln()` `ln(v instant-vector)` calculates the natural logarithm for all elements in `v`. Special cases are: * `ln(+Inf) = +Inf` * `ln(0) = -Inf` * `ln(x < 0) = NaN` * `ln(NaN) = NaN` ## `log2()` `log2(v instant-vector)` calculates the binary logarithm for all elements in `v`. The special cases are equivalent to those in `ln`. ## `log10()` `log10(v instant-vector)` calculates the decimal logarithm for all elements in `v`. The special cases are equivalent to those in `ln`. ## `minute()` `minute(v=vector(time()) instant-vector)` returns the minute of the hour for each of the given times in UTC. Returned values are from 0 to 59. ## `month()` `month(v=vector(time()) instant-vector)` returns the month of the year for each of the given times in UTC. Returned values are from 1 to 12, where 1 means January etc. ## `predict_linear()` `predict_linear(v range-vector, t scalar)` predicts the value of time series `t` seconds from now, based on the range vector `v`, using [simple linear regression](http://en.wikipedia.org/wiki/Simple_linear_regression). `predict_linear` should only be used with gauges. ## `rate()` `rate(v range-vector)` calculates the per-second average rate of increase of the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. Also, the calculation extrapolates to the ends of the time range, allowing for missed scrapes or imperfect alignment of scrape cycles with the range's time period. The following example expression returns the per-second rate of HTTP requests as measured over the last 5 minutes, per time series in the range vector: ``` rate(http_requests_total{job="api-server"}[5m]) ``` `rate` should only be used with counters. It is best suited for alerting, and for graphing of slow-moving counters. Note that when combining `rate()` with an aggregation operator (e.g. `sum()`) or a function aggregating over time (any function ending in `_over_time`), always take a `rate()` first, then aggregate. Otherwise `rate()` cannot detect counter resets when your target restarts. ## `resets()` For each input time series, `resets(v range-vector)` returns the number of counter resets within the provided time range as an instant vector. Any decrease in the value between two consecutive samples is interpreted as a counter reset. `resets` should only be used with counters. ## `round()` `round(v instant-vector, to_nearest=1 scalar)` rounds the sample values of all elements in `v` to the nearest integer. Ties are resolved by rounding up. The optional `to_nearest` argument allows specifying the nearest multiple to which the sample values should be rounded. This multiple may also be a fraction. ## `scalar()` Given a single-element input vector, `scalar(v instant-vector)` returns the sample value of that single element as a scalar. If the input vector does not have exactly one element, `scalar` will return `NaN`. ## `sort()` `sort(v instant-vector)` returns vector elements sorted by their sample values, in ascending order. ## `sort_desc()` Same as `sort`, but sorts in descending order. ## `sqrt()` `sqrt(v instant-vector)` calculates the square root of all elements in `v`. ## `time()` `time()` returns the number of seconds since January 1, 1970 UTC. Note that this does not actually return the current time, but the time at which the expression is to be evaluated. ## `timestamp()` `timestamp(v instant-vector)` returns the timestamp of each of the samples of the given vector as the number of seconds since January 1, 1970 UTC. *This function was added in Prometheus 2.0* ## `vector()` `vector(s scalar)` returns the scalar `s` as a vector with no labels. ## `year()` `year(v=vector(time()) instant-vector)` returns the year for each of the given times in UTC. ## `_over_time()` The following functions allow aggregating each series of a given range vector over time and return an instant vector with per-series aggregation results: * `avg_over_time(range-vector)`: the average value of all points in the specified interval. * `min_over_time(range-vector)`: the minimum value of all points in the specified interval. * `max_over_time(range-vector)`: the maximum value of all points in the specified interval. * `sum_over_time(range-vector)`: the sum of all values in the specified interval. * `count_over_time(range-vector)`: the count of all values in the specified interval. * `quantile_over_time(scalar, range-vector)`: the φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval. * `stddev_over_time(range-vector)`: the population standard deviation of the values in the specified interval. * `stdvar_over_time(range-vector)`: the population standard variance of the values in the specified interval. Note that all values in the specified interval have the same weight in the aggregation even if the values are not equally spaced throughout the interval. prometheus-2.1.0+ds/docs/querying/index.md000066400000000000000000000000451323116307200205300ustar00rootroot00000000000000--- title: Querying sort_rank: 4 --- prometheus-2.1.0+ds/docs/querying/operators.md000066400000000000000000000251311323116307200214420ustar00rootroot00000000000000--- title: Operators sort_rank: 2 --- # Operators ## Binary operators Prometheus's query language supports basic logical and arithmetic operators. For operations between two instant vectors, the [matching behavior](#vector-matching) can be modified. ### Arithmetic binary operators The following binary arithmetic operators exist in Prometheus: * `+` (addition) * `-` (subtraction) * `*` (multiplication) * `/` (division) * `%` (modulo) * `^` (power/exponentiation) Binary arithmetic operators are defined between scalar/scalar, vector/scalar, and vector/vector value pairs. **Between two scalars**, the behavior is obvious: they evaluate to another scalar that is the result of the operator applied to both scalar operands. **Between an instant vector and a scalar**, the operator is applied to the value of every data sample in the vector. E.g. if a time series instant vector is multiplied by 2, the result is another vector in which every sample value of the original vector is multiplied by 2. **Between two instant vectors**, a binary arithmetic operator is applied to each entry in the left-hand side vector and its [matching element](#vector-matching) in the right-hand vector. The result is propagated into the result vector and the metric name is dropped. Entries for which no matching entry in the right-hand vector can be found are not part of the result. ### Comparison binary operators The following binary comparison operators exist in Prometheus: * `==` (equal) * `!=` (not-equal) * `>` (greater-than) * `<` (less-than) * `>=` (greater-or-equal) * `<=` (less-or-equal) Comparison operators are defined between scalar/scalar, vector/scalar, and vector/vector value pairs. By default they filter. Their behaviour can be modified by providing `bool` after the operator, which will return `0` or `1` for the value rather than filtering. **Between two scalars**, the `bool` modifier must be provided and these operators result in another scalar that is either `0` (`false`) or `1` (`true`), depending on the comparison result. **Between an instant vector and a scalar**, these operators are applied to the value of every data sample in the vector, and vector elements between which the comparison result is `false` get dropped from the result vector. If the `bool` modifier is provided, vector elements that would be dropped instead have the value `0` and vector elements that would be kept have the value `1`. **Between two instant vectors**, these operators behave as a filter by default, applied to matching entries. Vector elements for which the expression is not true or which do not find a match on the other side of the expression get dropped from the result, while the others are propagated into a result vector with their original (left-hand side) metric names and label values. If the `bool` modifier is provided, vector elements that would have been dropped instead have the value `0` and vector elements that would be kept have the value `1` with the left-hand side metric names and label values. ### Logical/set binary operators These logical/set binary operators are only defined between instant vectors: * `and` (intersection) * `or` (union) * `unless` (complement) `vector1 and vector2` results in a vector consisting of the elements of `vector1` for which there are elements in `vector2` with exactly matching label sets. Other elements are dropped. The metric name and values are carried over from the left-hand side vector. `vector1 or vector2` results in a vector that contains all original elements (label sets + values) of `vector1` and additionally all elements of `vector2` which do not have matching label sets in `vector1`. `vector1 unless vector2` results in a vector consisting of the elements of `vector1` for which there are no elements in `vector2` with exactly matching label sets. All matching elements in both vectors are dropped. ## Vector matching Operations between vectors attempt to find a matching element in the right-hand side vector for each entry in the left-hand side. There are two basic types of matching behavior: One-to-one and many-to-one/one-to-many. ### One-to-one vector matches **One-to-one** finds a unique pair of entries from each side of the operation. In the default case, that is an operation following the format `vector1 vector2`. Two entries match if they have the exact same set of labels and corresponding values. The `ignoring` keyword allows ignoring certain labels when matching, while the `on` keyword allows reducing the set of considered labels to a provided list: ignoring(
    Element Value
    no data
    prometheus-2.1.0+ds/web/ui/static/js/graph/index.js000066400000000000000000000775151323116307200221670ustar00rootroot00000000000000var Prometheus = Prometheus || {}; var graphTemplate; var SECOND = 1000; /** * Graph */ Prometheus.Graph = function(element, options, handleChange, handleRemove) { this.el = element; this.graphHTML = null; this.options = options; this.handleChange = handleChange; this.handleRemove = function() { handleRemove(this); }; this.rickshawGraph = null; this.data = []; this.initialize(); }; Prometheus.Graph.timeFactors = { "y": 60 * 60 * 24 * 365, "w": 60 * 60 * 24 * 7, "d": 60 * 60 * 24, "h": 60 * 60, "m": 60, "s": 1 }; Prometheus.Graph.stepValues = [ "1s", "10s", "1m", "5m", "15m", "30m", "1h", "2h", "6h", "12h", "1d", "2d", "1w", "2w", "4w", "8w", "1y", "2y" ]; Prometheus.Graph.numGraphs = 0; Prometheus.Graph.prototype.initialize = function() { var self = this; self.id = Prometheus.Graph.numGraphs++; // Set default options. self.options.id = self.id; self.options.range_input = self.options.range_input || "1h"; if (self.options.tab === undefined) { self.options.tab = 1; } // Draw graph controls and container from Handlebars template. var options = { 'pathPrefix': PATH_PREFIX, 'buildVersion': BUILD_VERSION }; jQuery.extend(options, self.options); self.graphHTML = $(Mustache.render(graphTemplate, options)); self.el.append(self.graphHTML); // Get references to all the interesting elements in the graph container and // bind event handlers. var graphWrapper = self.el.find("#graph_wrapper" + self.id); self.queryForm = graphWrapper.find(".query_form"); self.expr = graphWrapper.find("textarea[name=expr]"); self.expr.keypress(function(e) { const enter = 13; if (e.which == enter && !e.shiftKey) { self.queryForm.submit(); e.preventDefault(); } // Auto-resize the text area on input. var offset = this.offsetHeight - this.clientHeight; var resizeTextarea = function(el) { $(el).css('height', 'auto').css('height', el.scrollHeight + offset); }; $(this).on('keyup input', function() { resizeTextarea(this); }); }); self.expr.change(self.handleChange); self.rangeInput = self.queryForm.find("input[name=range_input]"); self.stackedBtn = self.queryForm.find(".stacked_btn"); self.stacked = self.queryForm.find("input[name=stacked]"); self.insertMetric = self.queryForm.find("select[name=insert_metric]"); self.refreshInterval = self.queryForm.find("select[name=refresh]"); self.consoleTab = graphWrapper.find(".console"); self.graphTab = graphWrapper.find(".graph_container"); self.tabs = graphWrapper.find("a[data-toggle='tab']"); self.tabs.eq(self.options.tab).tab("show"); self.tabs.on("shown.bs.tab", function(e) { var target = $(e.target); self.options.tab = target.parent().index(); self.handleChange(); if ($("#" + target.attr("aria-controls")).hasClass("reload")) { self.submitQuery(); } }); // Return moves focus back to expr instead of submitting. self.insertMetric.bind("keydown", "return", function(e) { self.expr.focus(); self.expr.val(self.expr.val()); return e.preventDefault(); }) self.error = graphWrapper.find(".error").hide(); self.graphArea = graphWrapper.find(".graph_area"); self.graph = self.graphArea.find(".graph"); self.yAxis = self.graphArea.find(".y_axis"); self.legend = graphWrapper.find(".legend"); self.spinner = graphWrapper.find(".spinner"); self.evalStats = graphWrapper.find(".eval_stats"); self.endDate = graphWrapper.find("input[name=end_input]"); self.endDate.datetimepicker({ locale: 'en', format: 'YYYY-MM-DD HH:mm', toolbarPlacement: 'bottom', sideBySide: true, showTodayButton: true, showClear: true, showClose: true, timeZone: 'UTC', }); if (self.options.end_input) { self.endDate.data('DateTimePicker').date(self.options.end_input); } self.endDate.on("dp.change", function() { self.submitQuery(); }); self.refreshInterval.change(function() { self.updateRefresh(); }); self.isStacked = function() { return self.stacked.val() === '1'; }; var styleStackBtn = function() { var icon = self.stackedBtn.find('.glyphicon'); if (self.isStacked()) { self.stackedBtn.addClass("btn-primary"); icon.addClass("glyphicon-check"); icon.removeClass("glyphicon-unchecked"); } else { self.stackedBtn.removeClass("btn-primary"); icon.addClass("glyphicon-unchecked"); icon.removeClass("glyphicon-check"); } }; styleStackBtn(); self.stackedBtn.click(function() { if (self.isStacked() && self.graphJSON) { // If the graph was stacked, the original series data got mutated // (scaled) and we need to reconstruct it from the original JSON data. self.data = self.transformData(self.graphJSON); } self.stacked.val(self.isStacked() ? '0' : '1'); styleStackBtn(); self.updateGraph(); }); self.queryForm.submit(function() { self.consoleTab.addClass("reload"); self.graphTab.addClass("reload"); self.submitQuery(); return false; }); self.spinner.hide(); self.queryForm.find("button[name=inc_range]").click(function() { self.increaseRange(); }); self.queryForm.find("button[name=dec_range]").click(function() { self.decreaseRange(); }); self.queryForm.find("button[name=inc_end]").click(function() { self.increaseEnd(); }); self.queryForm.find("button[name=dec_end]").click(function() { self.decreaseEnd(); }); self.insertMetric.change(function() { self.expr.selection("replace", {text: self.insertMetric.val(), mode: "before"}); self.expr.focus(); // refocusing }); var removeBtn = graphWrapper.find("[name=remove]"); removeBtn.click(function() { self.remove(); return false; }); self.checkTimeDrift(); // initialize query history if (!localStorage.getItem("history")) { localStorage.setItem("history", JSON.stringify([])); } self.populateInsertableMetrics(); queryHistory.bindHistoryEvents(self); if (self.expr.val()) { self.submitQuery(); } }; Prometheus.Graph.prototype.checkTimeDrift = function() { var self = this; var browserTime = new Date().getTime() / 1000; $.ajax({ method: "GET", url: PATH_PREFIX + "/api/v1/query?query=time()", dataType: "json", success: function(json, textStatus) { if (json.status !== "success") { self.showError("Error querying time."); return; } var serverTime = json.data.result[0]; var diff = Math.abs(browserTime - serverTime); if (diff >= 30) { $("#graph_wrapper0").prepend( "
    Warning! Detected " + diff.toFixed(2) + " seconds time difference between your browser and the server. Prometheus relies on accurate time and time drift might cause unexpected query results.
    " ); } }, error: function() { self.showError("Error loading time."); } }); }; Prometheus.Graph.prototype.populateInsertableMetrics = function() { var self = this; $.ajax({ method: "GET", url: PATH_PREFIX + "/api/v1/label/__name__/values", dataType: "json", success: function(json, textStatus) { if (json.status !== "success") { self.showError("Error loading available metrics!"); return; } pageConfig.allMetrics = json.data; // todo: do we need self.allMetrics? Or can it just live on the page for (var i = 0; i < pageConfig.allMetrics.length; i++) { self.insertMetric[0].options.add(new Option(pageConfig.allMetrics[i], pageConfig.allMetrics[i])); } self.fuzzyResult = { query: null, result: null, map: {} } self.initTypeahead(self); }, error: function() { self.showError("Error loading available metrics!"); }, }); }; Prometheus.Graph.prototype.initTypeahead = function(self) { const historyIsChecked = $("div.query-history").hasClass("is-checked"); const source = historyIsChecked ? pageConfig.allMetrics.concat(JSON.parse(localStorage.getItem("history"))) : pageConfig.allMetrics; self.expr.typeahead({ source, items: "all", matcher: function (item) { // If we have result for current query, skip if (self.fuzzyResult.query !== this.query) { self.fuzzyResult.query = this.query; self.fuzzyResult.map = {}; self.fuzzyResult.result = fuzzy.filter(this.query.replace(/ /g, ""), this.source, { pre: "", post: "" }); self.fuzzyResult.result.forEach(function(r) { self.fuzzyResult.map[r.original] = r; }); } return item in self.fuzzyResult.map; }, sorter: function (items) { items.sort(function(a,b) { var i = self.fuzzyResult.map[b].score - self.fuzzyResult.map[a].score; return i === 0 ? a.localeCompare(b) : i; }); return items; }, highlighter: function (item) { return $("
    " + self.fuzzyResult.map[item].string + "
    "); }, }); // This needs to happen after attaching the typeahead plugin, as it // otherwise breaks the typeahead functionality. self.expr.focus(); } Prometheus.Graph.prototype.getOptions = function() { var self = this; var options = {}; var optionInputs = [ "range_input", "end_input", "step_input", "stacked" ]; self.queryForm.find("input").each(function(index, element) { var name = element.name; if ($.inArray(name, optionInputs) >= 0) { if (element.value.length > 0) { options[name] = element.value; } } }); options.expr = self.expr.val(); options.tab = self.options.tab; return options; }; Prometheus.Graph.prototype.parseDuration = function(rangeText) { var rangeRE = new RegExp("^([0-9]+)([ywdhms]+)$"); var matches = rangeText.match(rangeRE); if (!matches) { return; } if (matches.length != 3) { return 60; } var value = parseInt(matches[1]); var unit = matches[2]; return value * Prometheus.Graph.timeFactors[unit]; }; Prometheus.Graph.prototype.increaseRange = function() { var self = this; var rangeSeconds = self.parseDuration(self.rangeInput.val()); for (var i = 0; i < Prometheus.Graph.stepValues.length; i++) { if (rangeSeconds < self.parseDuration(Prometheus.Graph.stepValues[i])) { self.rangeInput.val(Prometheus.Graph.stepValues[i]); if (self.expr.val()) { self.submitQuery(); } return; } } }; Prometheus.Graph.prototype.decreaseRange = function() { var self = this; var rangeSeconds = self.parseDuration(self.rangeInput.val()); for (var i = Prometheus.Graph.stepValues.length - 1; i >= 0; i--) { if (rangeSeconds > self.parseDuration(Prometheus.Graph.stepValues[i])) { self.rangeInput.val(Prometheus.Graph.stepValues[i]); if (self.expr.val()) { self.submitQuery(); } return; } } }; Prometheus.Graph.prototype.getEndDate = function() { var self = this; if (!self.endDate || !self.endDate.val()) { return moment(); } return self.endDate.data('DateTimePicker').date(); }; Prometheus.Graph.prototype.getOrSetEndDate = function() { var self = this; var date = self.getEndDate(); self.setEndDate(date); return date; }; Prometheus.Graph.prototype.setEndDate = function(date) { var self = this; self.endDate.data('DateTimePicker').date(date); }; Prometheus.Graph.prototype.increaseEnd = function() { var self = this; var newDate = moment(self.getOrSetEndDate()); newDate.add(self.parseDuration(self.rangeInput.val()) / 2, 'seconds'); self.setEndDate(newDate); self.submitQuery(); }; Prometheus.Graph.prototype.decreaseEnd = function() { var self = this; var newDate = moment(self.getOrSetEndDate()); newDate.subtract(self.parseDuration(self.rangeInput.val()) / 2, 'seconds'); self.setEndDate(newDate); self.submitQuery(); }; Prometheus.Graph.prototype.submitQuery = function() { var self = this; self.clearError(); if (!self.expr.val()) { return; } self.spinner.show(); self.evalStats.empty(); var startTime = new Date().getTime(); var rangeSeconds = self.parseDuration(self.rangeInput.val()); var resolution = parseInt(self.queryForm.find("input[name=step_input]").val()) || Math.max(Math.floor(rangeSeconds / 250), 1); var endDate = self.getEndDate() / 1000; if (self.queryXhr) { self.queryXhr.abort(); } var url; var success; var params = { "query": self.expr.val() }; if (self.options.tab === 0) { params.start = endDate - rangeSeconds; params.end = endDate; params.step = resolution; url = PATH_PREFIX + "/api/v1/query_range"; success = function(json, textStatus) { self.handleGraphResponse(json, textStatus); }; } else { params.time = startTime / 1000; url = PATH_PREFIX + "/api/v1/query"; success = function(json, textStatus) { self.handleConsoleResponse(json, textStatus); }; } self.params = params; self.queryXhr = $.ajax({ method: self.queryForm.attr("method"), url: url, dataType: "json", data: params, success: function(json, textStatus) { if (json.status !== "success") { self.showError(json.error); return; } queryHistory.handleHistory(self); success(json.data, textStatus); }, error: function(xhr, resp) { if (resp != "abort") { var err; if (xhr.responseJSON !== undefined) { err = xhr.responseJSON.error; } else { err = xhr.statusText; } self.showError("Error executing query: " + err); } }, complete: function(xhr, resp) { if (resp == "abort") { return; } var duration = new Date().getTime() - startTime; var totalTimeSeries = 0; if (xhr.responseJSON.data !== undefined) { if (xhr.responseJSON.data.resultType === "scalar") { totalTimeSeries = 1; } else if(xhr.responseJSON.data.result !== null) { totalTimeSeries = xhr.responseJSON.data.result.length; } } self.evalStats.html("Load time: " + duration + "ms
    Resolution: " + resolution + "s
    " + "Total time series: " + totalTimeSeries); self.spinner.hide(); } }); }; Prometheus.Graph.prototype.showError = function(msg) { var self = this; self.error.text(msg); self.error.show(); }; Prometheus.Graph.prototype.clearError = function(msg) { var self = this; self.error.text(''); self.error.hide(); }; Prometheus.Graph.prototype.updateRefresh = function() { var self = this; if (self.timeoutID) { window.clearTimeout(self.timeoutID); } interval = self.parseDuration(self.refreshInterval.val()); if (!interval) { return; } self.timeoutID = window.setTimeout(function() { self.submitQuery(); self.updateRefresh(); }, interval * SECOND); }; Prometheus.Graph.prototype.renderLabels = function(labels) { var labelStrings = []; for (var label in labels) { if (label != "__name__") { labelStrings.push("" + label + ": " + escapeHTML(labels[label])); } } return labels = "
    " + labelStrings.join("
    ") + "
    "; }; Prometheus.Graph.prototype.metricToTsName = function(labels) { var tsName = (labels.__name__ || '') + "{"; var labelStrings = []; for (var label in labels) { if (label != "__name__") { labelStrings.push(label + "=\"" + labels[label] + "\""); } } tsName += labelStrings.join(",") + "}"; return tsName; }; Prometheus.Graph.prototype.parseValue = function(value) { var val = parseFloat(value); if (isNaN(val)) { // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). The // can't be graphed, so show them as gaps (null). return null; } return val; }; Prometheus.Graph.prototype.transformData = function(json) { var self = this; var palette = new Rickshaw.Color.Palette(); if (json.resultType != "matrix") { self.showError("Result is not of matrix type! Please enter a correct expression."); return []; } var data = json.result.map(function(ts) { var name; var labels; if (ts.metric === null) { name = "scalar"; labels = {}; } else { name = escapeHTML(self.metricToTsName(ts.metric)); labels = ts.metric; } return { name: name, labels: labels, data: ts.values.map(function(value) { return { x: value[0], y: self.parseValue(value[1]) }; }), color: palette.color() }; }); data.forEach(function(s) { // Insert nulls for all missing steps. var newSeries = []; var pos = 0; for (var t = self.params.start; t <= self.params.end; t += self.params.step) { // Allow for floating point inaccuracy. if (s.data.length > pos && s.data[pos].x < t + self.params.step / 100) { newSeries.push(s.data[pos]); pos++; } else { newSeries.push({x: t, y: null}); } } s.data = newSeries; }); return data; }; Prometheus.Graph.prototype.updateGraph = function() { var self = this; if (self.data.length === 0) { return; } // Remove any traces of an existing graph. self.legend.empty(); if (self.graphArea.children().length > 0) { self.graph.remove(); self.yAxis.remove(); } self.graph = $('
    '); self.yAxis = $('
    '); self.graphArea.append(self.graph); self.graphArea.append(self.yAxis); var endTime = self.getEndDate() / 1000; // Convert to UNIX timestamp. var duration = self.parseDuration(self.rangeInput.val()) || 3600; // 1h default. var startTime = endTime - duration; self.data.forEach(function(s) { // Padding series with invisible "null" values at the configured x-axis boundaries ensures // that graphs are displayed with a fixed x-axis range instead of snapping to the available // time range in the data. if (s.data[0].x > startTime) { s.data.unshift({x: startTime, y: null}); } if (s.data[s.data.length - 1].x < endTime) { s.data.push({x: endTime, y: null}); } }); // Now create the new graph. self.rickshawGraph = new Rickshaw.Graph({ element: self.graph[0], height: Math.max(self.graph.innerHeight(), 100), width: Math.max(self.graph.innerWidth() - 80, 200), renderer: (self.isStacked() ? "stack" : "line"), interpolation: "linear", series: self.data, min: "auto", }); // Find and set graph's max/min if (self.isStacked() === true) { // When stacked is toggled var max = 0; self.data.forEach(function(timeSeries) { var currSeriesMax = 0; timeSeries.data.forEach(function(dataPoint) { if (dataPoint.y > currSeriesMax && dataPoint.y != null) { currSeriesMax = dataPoint.y; } }); max += currSeriesMax; }); self.rickshawGraph.max = max*1.05; self.rickshawGraph.min = 0; } else { var min = Infinity; var max = -Infinity; self.data.forEach(function(timeSeries) { timeSeries.data.forEach(function(dataPoint) { if (dataPoint.y < min && dataPoint.y != null) { min = dataPoint.y; } if (dataPoint.y > max && dataPoint.y != null) { max = dataPoint.y; } }); }); if (min === max) { self.rickshawGraph.max = max + 1; self.rickshawGraph.min = min - 1; } else { self.rickshawGraph.max = max + (0.1*(Math.abs(max - min))); self.rickshawGraph.min = min - (0.1*(Math.abs(max - min))); } } var xAxis = new Rickshaw.Graph.Axis.Time({ graph: self.rickshawGraph }); var yAxis = new Rickshaw.Graph.Axis.Y({ graph: self.rickshawGraph, orientation: "left", tickFormat: this.formatKMBT, element: self.yAxis[0], }); self.rickshawGraph.render(); var hoverDetail = new Rickshaw.Graph.HoverDetail({ graph: self.rickshawGraph, formatter: function(series, x, y) { var date = '' + new Date(x * 1000).toUTCString() + ''; var swatch = ''; var content = swatch + (series.labels.__name__ || 'value') + ": " + y + ''; return date + '
    ' + content + '
    ' + self.renderLabels(series.labels); } }); var legend = new Rickshaw.Graph.Legend({ element: self.legend[0], graph: self.rickshawGraph, }); var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight( { graph: self.rickshawGraph, legend: legend }); var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({ graph: self.rickshawGraph, legend: legend }); self.handleChange(); }; Prometheus.Graph.prototype.resizeGraph = function() { var self = this; if (self.rickshawGraph !== null) { self.rickshawGraph.configure({ width: Math.max(self.graph.innerWidth() - 80, 200), }); self.rickshawGraph.render(); } }; Prometheus.Graph.prototype.handleGraphResponse = function(json, textStatus) { var self = this; // Rickshaw mutates passed series data for stacked graphs, so we need to save // the original AJAX response in order to re-transform it into series data // when the user disables the stacking. self.graphJSON = json; self.data = self.transformData(json); if (self.data.length === 0) { self.showError("No datapoints found."); return; } self.graphTab.removeClass("reload"); self.updateGraph(); }; Prometheus.Graph.prototype.handleConsoleResponse = function(data, textStatus) { var self = this; self.consoleTab.removeClass("reload"); self.graphJSON = null; var tBody = self.consoleTab.find(".console_table tbody"); tBody.empty(); switch(data.resultType) { case "vector": if (data.result === null || data.result.length === 0) { tBody.append("no data"); return; } for (var i = 0; i < data.result.length; i++) { var s = data.result[i]; var tsName = self.metricToTsName(s.metric); tBody.append("" + escapeHTML(tsName) + "" + s.value[1] + ""); } break; case "matrix": if (data.result.length === 0) { tBody.append("no data"); return; } for (var i = 0; i < data.result.length; i++) { var v = data.result[i]; var tsName = self.metricToTsName(v.metric); var valueText = ""; for (var j = 0; j < v.values.length; j++) { valueText += v.values[j][1] + " @" + v.values[j][0] + "
    "; } tBody.append("" + escapeHTML(tsName) + "" + valueText + ""); } break; case "scalar": tBody.append("scalar" + data.result[1] + ""); break; case "string": tBody.append("string" + escapeHTML(data.result[1]) + ""); break; default: self.showError("Unsupported value type!"); break; } }; Prometheus.Graph.prototype.remove = function() { var self = this; $(self.graphHTML).remove(); self.handleRemove(); self.handleChange(); }; Prometheus.Graph.prototype.formatKMBT = function(y) { var abs_y = Math.abs(y); if (abs_y >= 1e24) { return (y / 1e24).toString() + "Y"; } else if (abs_y >= 1e21) { return (y / 1e21).toString() + "Z"; } else if (abs_y >= 1e18) { return (y / 1e18).toString() + "E"; } else if (abs_y >= 1e15) { return (y / 1e15).toString() + "P"; } else if (abs_y >= 1e12) { return (y / 1e12).toString() + "T"; } else if (abs_y >= 1e9) { return (y / 1e9).toString() + "G"; } else if (abs_y >= 1e6) { return (y / 1e6).toString() + "M"; } else if (abs_y >= 1e3) { return (y / 1e3).toString() + "k"; } else if (abs_y >= 1) { return y } else if (abs_y === 0) { return y } else if (abs_y <= 1e-24) { return (y / 1e-24).toString() + "y"; } else if (abs_y <= 1e-21) { return (y / 1e-21).toString() + "z"; } else if (abs_y <= 1e-18) { return (y / 1e-18).toString() + "a"; } else if (abs_y <= 1e-15) { return (y / 1e-15).toString() + "f"; } else if (abs_y <= 1e-12) { return (y / 1e-12).toString() + "p"; } else if (abs_y <= 1e-9) { return (y / 1e-9).toString() + "n"; } else if (abs_y <= 1e-6) { return (y / 1e-6).toString() + "µ"; } else if (abs_y <=1e-3) { return (y / 1e-3).toString() + "m"; } else if (abs_y <= 1) { return y } } /** * Page */ const pageConfig = { graphs: [] }; Prometheus.Page = function() {}; Prometheus.Page.prototype.init = function() { var graphOptions = this.parseURL(); if (graphOptions.length === 0) { graphOptions.push({}); } graphOptions.forEach(this.addGraph, this); $("#add_graph").click(this.addGraph.bind(this, {})); }; Prometheus.Page.prototype.parseURL = function() { if (window.location.search == "") { return []; } var queryParams = window.location.search.substring(1).split('&'); var queryParamHelper = new Prometheus.Page.QueryParamHelper(); return queryParamHelper.parseQueryParams(queryParams); }; Prometheus.Page.prototype.addGraph = function(options) { var graph = new Prometheus.Graph( $("#graph_container"), options, this.updateURL.bind(this), this.removeGraph.bind(this) ); // this.graphs.push(graph); pageConfig.graphs.push(graph); $(window).resize(function() { graph.resizeGraph(); }); }; // NOTE: This needs to be kept in sync with /util/strutil/strconv.go:GraphLinkForExpression Prometheus.Page.prototype.updateURL = function() { var queryString = pageConfig.graphs.map(function(graph, index) { var graphOptions = graph.getOptions(); var queryParamHelper = new Prometheus.Page.QueryParamHelper(); var queryObject = queryParamHelper.generateQueryObject(graphOptions, index); return $.param(queryObject); }, this).join("&"); history.pushState({}, "", "graph?" + queryString); }; Prometheus.Page.prototype.removeGraph = function(graph) { pageConfig.graphs = pageConfig.graphs.filter(function(g) {return g !== graph}); }; Prometheus.Page.QueryParamHelper = function() {}; Prometheus.Page.QueryParamHelper.prototype.parseQueryParams = function(queryParams) { var orderedQueryParams = this.filterInvalidParams(queryParams).sort(); return this.fetchOptionsFromOrderedParams(orderedQueryParams, 0); }; Prometheus.Page.QueryParamHelper.queryParamFormat = /^g\d+\..+=.+$/; Prometheus.Page.QueryParamHelper.prototype.filterInvalidParams = function(paramTuples) { return paramTuples.filter(function(paramTuple) { return Prometheus.Page.QueryParamHelper.queryParamFormat.test(paramTuple); }); }; Prometheus.Page.QueryParamHelper.prototype.fetchOptionsFromOrderedParams = function(queryParams, graphIndex) { if (queryParams.length == 0) { return []; } var prefixOfThisIndex = this.queryParamPrefix(graphIndex); var numberOfParamsForThisGraph = queryParams.filter(function(paramTuple) { return paramTuple.startsWith(prefixOfThisIndex); }).length; if (numberOfParamsForThisGraph == 0) { return []; } var paramsForThisGraph = queryParams.splice(0, numberOfParamsForThisGraph); paramsForThisGraph = paramsForThisGraph.map(function(paramTuple) { return paramTuple.substring(prefixOfThisIndex.length); }); var options = this.parseQueryParamsOfOneGraph(paramsForThisGraph); var optionAccumulator = this.fetchOptionsFromOrderedParams(queryParams, graphIndex + 1); optionAccumulator.unshift(options); return optionAccumulator; }; Prometheus.Page.QueryParamHelper.prototype.parseQueryParamsOfOneGraph = function(queryParams) { var options = {}; queryParams.forEach(function(tuple) { var optionNameAndValue = tuple.split('='); var optionName = optionNameAndValue[0]; var optionValue = decodeURIComponent(optionNameAndValue[1].replace(/\+/g, " ")); if (optionName == "tab") { optionValue = parseInt(optionValue); // tab is integer } options[optionName] = optionValue; }); return options; }; Prometheus.Page.QueryParamHelper.prototype.queryParamPrefix = function(index) { return "g" + index + "."; }; Prometheus.Page.QueryParamHelper.prototype.generateQueryObject = function(graphOptions, index) { var prefix = this.queryParamPrefix(index); var queryObject = {}; Object.keys(graphOptions).forEach(function(key) { queryObject[prefix + key] = graphOptions[key]; }); return queryObject; }; // These two methods (isDeprecatedGraphURL and redirectToMigratedURL) // are added only for backward compatibility to old query format. function isDeprecatedGraphURL() { if (window.location.hash.length == 0) { return false; } var decodedFragment = decodeURIComponent(window.location.hash); try { JSON.parse(decodedFragment.substr(1)); // drop the hash # } catch (e) { return false; } return true; } function redirectToMigratedURL() { var decodedFragment = decodeURIComponent(window.location.hash); var graphOptions = JSON.parse(decodedFragment.substr(1)); // drop the hash # var queryObject = {}; graphOptions.map(function(options, index){ var prefix = "g" + index + "."; Object.keys(options).forEach(function(key) { queryObject[prefix + key] = options[key]; }); }); var query = $.param(queryObject); window.location = PATH_PREFIX + "/graph?" + query; } /** * Query History helper functions * **/ const queryHistory = { bindHistoryEvents: function(graph) { const targetEl = $('div.query-history'); const icon = $(targetEl).children('i'); targetEl.off('click'); if (JSON.parse(localStorage.getItem('enable-query-history'))) { this.toggleOn(targetEl); } targetEl.on('click', function() { if (icon.hasClass('glyphicon-unchecked')) { queryHistory.toggleOn(targetEl); } else if (icon.hasClass('glyphicon-check')) { queryHistory.toggleOff(targetEl); } }); }, handleHistory: function(graph) { const query = graph.expr.val(); const isSimpleMetric = pageConfig.allMetrics.indexOf(query) !== -1; if (isSimpleMetric) { return; } let parsedQueryHistory = JSON.parse(localStorage.getItem('history')); const hasStoredQuery = parsedQueryHistory.indexOf(query) !== -1; if (hasStoredQuery) { parsedQueryHistory.splice(parsedQueryHistory.indexOf(query), 1); } parsedQueryHistory.push(query); const queryCount = parsedQueryHistory.length; parsedQueryHistory = parsedQueryHistory.slice(queryCount - 50, queryCount); localStorage.setItem('history', JSON.stringify(parsedQueryHistory)); this.updateTypeaheadMetricSet(parsedQueryHistory); }, toggleOn: function(targetEl) { this.updateTypeaheadMetricSet(JSON.parse(localStorage.getItem('history'))); $(targetEl).children('i').removeClass('glyphicon-unchecked').addClass('glyphicon-check'); targetEl.addClass('is-checked'); localStorage.setItem('enable-query-history', true); }, toggleOff: function(targetEl) { this.updateTypeaheadMetricSet(); $(targetEl).children('i').removeClass('glyphicon-check').addClass('glyphicon-unchecked'); targetEl.removeClass('is-checked'); localStorage.setItem('enable-query-history', false); }, updateTypeaheadMetricSet: function(metricSet) { pageConfig.graphs.forEach(function(graph) { graph.expr.data('typeahead').source = metricSet ? pageConfig.allMetrics.concat(metricSet) : pageConfig.allMetrics; }); } }; function escapeHTML(string) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }; return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; }); } function init() { $.ajaxSetup({ cache: false }); $.ajax({ url: PATH_PREFIX + "/static/js/graph/graph_template.handlebar?v=" + BUILD_VERSION, success: function(data) { graphTemplate = data; Mustache.parse(data); if (isDeprecatedGraphURL()) { redirectToMigratedURL(); } else { var Page = new Prometheus.Page(); Page.init(); } } }); } $(init); prometheus-2.1.0+ds/web/ui/static/js/prom_console.js000066400000000000000000000537151323116307200224520ustar00rootroot00000000000000/* * Functions to make it easier to write prometheus consoles, such * as graphs. * */ PromConsole = {}; PromConsole.NumberFormatter = {}; PromConsole.NumberFormatter.prefixesBig = ["k", "M", "G", "T", "P", "E", "Z", "Y"]; PromConsole.NumberFormatter.prefixesBig1024 = ["ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]; PromConsole.NumberFormatter.prefixesSmall = ["m", "u", "n", "p", "f", "a", "z", "y"]; PromConsole._stripTrailingZero = function(x) { if (x.indexOf("e") == -1) { // It's not safe to strip if it's scientific notation. return x.replace(/\.?0*$/, ''); } return x; }; // Humanize a number. PromConsole.NumberFormatter.humanize = function(x) { var ret = PromConsole.NumberFormatter._humanize( x, PromConsole.NumberFormatter.prefixesBig, PromConsole.NumberFormatter.prefixesSmall, 1000); x = ret[0]; var prefix = ret[1]; if (Math.abs(x) < 1) { return x.toExponential(3) + prefix; } return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; }; // Humanize a number, don't use milli/micro/etc. prefixes. PromConsole.NumberFormatter.humanizeNoSmallPrefix = function(x) { if (Math.abs(x) < 1) { return PromConsole._stripTrailingZero(x.toPrecision(3)); } var ret = PromConsole.NumberFormatter._humanize( x, PromConsole.NumberFormatter.prefixesBig, [], 1000); x = ret[0]; var prefix = ret[1]; return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; }; // Humanize a number with 1024 as the base, rather than 1000. PromConsole.NumberFormatter.humanize1024 = function(x) { var ret = PromConsole.NumberFormatter._humanize( x, PromConsole.NumberFormatter.prefixesBig1024, [], 1024); x = ret[0]; var prefix = ret[1]; if (Math.abs(x) < 1) { return x.toExponential(3) + prefix; } return PromConsole._stripTrailingZero(x.toFixed(3)) + prefix; }; // Humanize a number, returning an exact representation. PromConsole.NumberFormatter.humanizeExact = function(x) { var ret = PromConsole.NumberFormatter._humanize( x, PromConsole.NumberFormatter.prefixesBig, PromConsole.NumberFormatter.prefixesSmall, 1000); return ret[0] + ret[1]; }; PromConsole.NumberFormatter._humanize = function(x, prefixesBig, prefixesSmall, factor) { var prefix = ""; if (x === 0) { /* Do nothing. */ } else if (Math.abs(x) >= 1) { for (var i=0; i < prefixesBig.length && Math.abs(x) >= factor; ++i) { x /= factor; prefix = prefixesBig[i]; } } else { for (var i=0; i < prefixesSmall.length && Math.abs(x) < 1; ++i) { x *= factor; prefix = prefixesSmall[i]; } } return [x, prefix]; }; PromConsole.TimeControl = function() { document.getElementById("prom_graph_duration_shrink").onclick = this.decreaseDuration.bind(this); document.getElementById("prom_graph_duration_grow").onclick = this.increaseDuration.bind(this); document.getElementById("prom_graph_time_back").onclick = this.decreaseEnd.bind(this); document.getElementById("prom_graph_time_forward").onclick = this.increaseEnd.bind(this); document.getElementById("prom_graph_refresh_button").onclick = this.refresh.bind(this); this.durationElement = document.getElementById("prom_graph_duration"); this.endElement = document.getElementById("prom_graph_time_end"); this.durationElement.oninput = this.dispatch.bind(this); this.endElement.oninput = this.dispatch.bind(this); this.endElement.oninput = this.dispatch.bind(this); this.refreshValueElement = document.getElementById("prom_graph_refresh_button_value"); var refreshList = document.getElementById("prom_graph_refresh_intervals"); var refreshIntervals = ["Off", "1m", "5m", "15m", "1h"]; for (var i=0; i < refreshIntervals.length; ++i) { var li = document.createElement("li"); li.onclick = this.setRefresh.bind(this, refreshIntervals[i]); li.textContent = refreshIntervals[i]; refreshList.appendChild(li); } this.durationElement.value = PromConsole.TimeControl.prototype.getHumanDuration( PromConsole.TimeControl._initialValues.duration); if (!PromConsole.TimeControl._initialValues.endTimeNow) { this.endElement.value = PromConsole.TimeControl.prototype.getHumanDate( new Date(PromConsole.TimeControl._initialValues.endTime * 1000)); } }; PromConsole.TimeControl.timeFactors = { "y": 60 * 60 * 24 * 365, "w": 60 * 60 * 24 * 7, "d": 60 * 60 * 24, "h": 60 * 60, "m": 60, "s": 1 }; PromConsole.TimeControl.stepValues = [ "10s", "1m", "5m", "15m", "30m", "1h", "2h", "6h", "12h", "1d", "2d", "1w", "2w", "4w", "8w", "1y", "2y" ]; PromConsole.TimeControl.prototype._setHash = function() { var duration = this.parseDuration(this.durationElement.value); var endTime = this.getEndDate() / 1000; window.location.hash = "#pctc" + encodeURIComponent(JSON.stringify( {duration: duration, endTime: endTime})); }; PromConsole.TimeControl._initialValues = function() { var hash = window.location.hash; var values; if (hash.indexOf('#pctc') === 0) { values = JSON.parse(decodeURIComponent(hash.substring(5))); } else { values = {duration: 3600, endTime: 0}; } if (values.endTime == 0) { values.endTime = new Date().getTime() / 1000; values.endTimeNow = true; } return values; }(); PromConsole.TimeControl.prototype.parseDuration = function(durationText) { var durationRE = new RegExp("^([0-9]+)([ywdhms]?)$"); var matches = durationText.match(durationRE); if (!matches) { return 3600; } var value = parseInt(matches[1]); var unit = matches[2] || 's'; return value * PromConsole.TimeControl.timeFactors[unit]; }; PromConsole.TimeControl.prototype.getHumanDuration = function(duration) { var units = []; for (var key in PromConsole.TimeControl.timeFactors) { units.push([PromConsole.TimeControl.timeFactors[key], key]); } units.sort(function(a, b) { return b[0] - a[0]; }); for (var i = 0; i < units.length; ++i) { if (duration % units[i][0] === 0) { return (duration / units[i][0]) + units[i][1]; } } return duration; }; PromConsole.TimeControl.prototype.increaseDuration = function() { var durationSeconds = this.parseDuration(this.durationElement.value); for (var i = 0; i < PromConsole.TimeControl.stepValues.length; i++) { if (durationSeconds < this.parseDuration(PromConsole.TimeControl.stepValues[i])) { this.setDuration(PromConsole.TimeControl.stepValues[i]); this.dispatch(); return; } } }; PromConsole.TimeControl.prototype.decreaseDuration = function() { var durationSeconds = this.parseDuration(this.durationElement.value); for (var i = PromConsole.TimeControl.stepValues.length - 1; i >= 0; i--) { if (durationSeconds > this.parseDuration(PromConsole.TimeControl.stepValues[i])) { this.setDuration(PromConsole.TimeControl.stepValues[i]); this.dispatch(); return; } } }; PromConsole.TimeControl.prototype.setDuration = function(duration) { this.durationElement.value = duration; this._setHash(); }; PromConsole.TimeControl.prototype.getEndDate = function() { if (this.endElement.value === '') { return null; } var dateParts = this.endElement.value.split(/[- :]/) return Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2], dateParts[3], dateParts[4]); }; PromConsole.TimeControl.prototype.getOrSetEndDate = function() { var date = this.getEndDate(); if (date) { return date; } date = new Date(); this.setEndDate(date); return date.getTime(); }; PromConsole.TimeControl.prototype.getHumanDate = function(date) { var hours = date.getUTCHours() < 10 ? '0' + date.getUTCHours() : date.getUTCHours(); var minutes = date.getUTCMinutes() < 10 ? '0' + date.getUTCMinutes() : date.getUTCMinutes(); return date.getUTCFullYear() + "-" + (date.getUTCMonth()+1) + "-" + date.getUTCDate() + " " + hours + ":" + minutes; }; PromConsole.TimeControl.prototype.setEndDate = function(date) { this.setRefresh("Off"); this.endElement.value = this.getHumanDate(date); this._setHash(); }; PromConsole.TimeControl.prototype.increaseEnd = function() { // Increase duration 25% range & convert ms to s. this.setEndDate(new Date(this.getOrSetEndDate() + this.parseDuration(this.durationElement.value) * 1000/4 )); this.dispatch(); }; PromConsole.TimeControl.prototype.decreaseEnd = function() { this.setEndDate(new Date(this.getOrSetEndDate() - this.parseDuration(this.durationElement.value) * 1000/4 )); this.dispatch(); }; PromConsole.TimeControl.prototype.refresh = function() { this.endElement.value = ''; this._setHash(); this.dispatch(); }; PromConsole.TimeControl.prototype.dispatch = function() { var durationSeconds = this.parseDuration(this.durationElement.value); var end = this.getEndDate(); if (end === null) { end = new Date().getTime(); } for (var i = 0; i< PromConsole._graph_registry.length; i++) { var graph = PromConsole._graph_registry[i]; graph.params.duration = durationSeconds; graph.params.endTime = end / 1000; graph.dispatch(); } }; PromConsole.TimeControl.prototype._refreshInterval = null; PromConsole.TimeControl.prototype.setRefresh = function(duration) { if (this._refreshInterval !== null) { window.clearInterval(this._refreshInterval); this._refreshInterval = null; } if (duration != "Off") { if (this.endElement.value !== '') { this.refresh(); } var durationSeconds = this.parseDuration(duration); this._refreshInterval = window.setInterval(this.dispatch.bind(this), durationSeconds * 1000); } this.refreshValueElement.textContent = duration; }; // List of all graphs, used by time controls. PromConsole._graph_registry = []; PromConsole.graphDefaults = { expr: null, // Expression to graph. Can be a list of strings. node: null, // DOM node to place graph under. // How long the graph is over, in seconds. duration: PromConsole.TimeControl._initialValues.duration, // The unixtime the graph ends at. endTime: PromConsole.TimeControl._initialValues.endTime, width: null, // Height of the graph div, excluding titles and legends. // Defaults to auto-detection. height: 200, // Height of the graph div, excluding titles and legends. min: "auto", // Minimum Y-axis value, defaults to lowest data value. max: undefined, // Maximum Y-axis value, defaults to highest data value. renderer: 'line', // Type of graphs, options are 'line' and 'area'. name: null, // What to call plots, defaults to trying to do // something reasonable. // If a string, it'll use that. [[ label ]] will be substituted. // If a function it'll be called with a map of keys to values, // and should return the name to use. // Can be a list of strings/functions, each element // will be applied to the plots from the corresponding // element of the expr list. xTitle: "Time", // The title of the x axis. yUnits: "", // The units of the y axis. yTitle: "", // The title of the y axis. // Number formatter for y axis. yAxisFormatter: PromConsole.NumberFormatter.humanize, // Number formatter for y values hover detail. yHoverFormatter: PromConsole.NumberFormatter.humanizeExact, // Color scheme to be used by the plots. Can be either a list of hex color // codes or one of the color scheme names supported by Rickshaw. colorScheme: null, }; PromConsole.Graph = function(params) { for (var k in PromConsole.graphDefaults) { if (!(k in params)) { params[k] = PromConsole.graphDefaults[k]; } } if (typeof params.expr == "string") { params.expr = [params.expr]; } if (typeof params.name == "string" || typeof params.name == "function") { var name = []; for (var i = 0; i < params.expr.length; i++) { name.push(params.name); } params.name = name; } this.params = params; this.rendered_data = null; // Keep a reference so that further updates (e.g. annotations) can be made // by the user in their templates. this.rickshawGraph = null; PromConsole._graph_registry.push(this); /* * Table layout: * | yTitle | Graph | * | | xTitle | * | /graph | Legend | */ var table = document.createElement("table"); table.className = "prom_graph_table"; params.node.appendChild(table); var tr = document.createElement("tr"); table.appendChild(tr); var yTitleTd = document.createElement("td"); tr.appendChild(yTitleTd); var yTitleDiv = document.createElement("td"); yTitleTd.appendChild(yTitleDiv); yTitleDiv.className = "prom_graph_ytitle"; yTitleDiv.textContent = params.yTitle + (params.yUnits ? " (" + params.yUnits.trim() + ")" : ""); this.graphTd = document.createElement("td"); tr.appendChild(this.graphTd); this.graphTd.className = "rickshaw_graph"; this.graphTd.width = params.width; this.graphTd.height = params.height; tr = document.createElement("tr"); table.appendChild(tr); tr.appendChild(document.createElement("td")); var xTitleTd = document.createElement("td"); tr.appendChild(xTitleTd); xTitleTd.className = "prom_graph_xtitle"; xTitleTd.textContent = params.xTitle; tr = document.createElement("tr"); table.appendChild(tr); var graphLinkTd = document.createElement("td"); tr.appendChild(graphLinkTd); var graphLinkA = document.createElement("a"); graphLinkTd.appendChild(graphLinkA); graphLinkA.className = "prom_graph_link"; graphLinkA.textContent = "+"; graphLinkA.href = PromConsole._graphsToSlashGraphURL(params.expr); var legendTd = document.createElement("td"); tr.appendChild(legendTd); this.legendDiv = document.createElement("div"); legendTd.width = params.width; legendTd.appendChild(this.legendDiv); window.addEventListener('resize', function() { if(this.rendered_data !== null) { this._render(this.rendered_data); } }.bind(this)); this.dispatch(); }; PromConsole.Graph.prototype._parseValue = function(value) { var val = parseFloat(value); if (isNaN(val)) { // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). The // can't be graphed, so show them as gaps (null). return null; } return val; }; PromConsole.Graph.prototype._escapeHTML = function(string) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }; return string.replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; }); }; PromConsole.Graph.prototype._render = function(data) { var self = this; var palette = new Rickshaw.Color.Palette({scheme: this.params.colorScheme}); var series = []; // This will be used on resize. this.rendered_data = data; var nameFuncs = []; if (this.params.name === null) { var chooser = PromConsole._chooseNameFunction(data); for (var i = 0; i < this.params.expr.length; i++) { nameFuncs.push(chooser); } } else { for (var i = 0; i < this.params.name.length; i++) { if (typeof this.params.name[i] == "string") { nameFuncs.push(function(i, metric) { return PromConsole._interpolateName(this.params.name[i], metric); }.bind(this, i)); } else { nameFuncs.push(this.params.name[i]); } } } // Get the data into the right format. var seriesLen = 0; for (var e = 0; e < data.length; e++) { for (var i = 0; i < data[e].data.result.length; i++) { series[seriesLen] = { data: data[e].data.result[i].values.map(function(s) { return {x: s[0], y: self._parseValue(s[1])}; }), color: palette.color(), name: self._escapeHTML(nameFuncs[e](data[e].data.result[i].metric)), }; // Insert nulls for all missing steps. var newSeries = []; var pos = 0; var start = self.params.endTime - self.params.duration; var step = self.params.duration / this.graphTd.offsetWidth; for (var t = start; t <= self.params.endTime; t += step) { // Allow for floating point inaccuracy. if (series[seriesLen].data.length > pos && series[seriesLen].data[pos].x < t + step / 100) { newSeries.push(series[seriesLen].data[pos]); pos++; } else { newSeries.push({x: t, y: null}); } } series[seriesLen].data = newSeries; seriesLen++; } } this._clearGraph(); if (!series.length) { var errorText = document.createElement("div"); errorText.className = 'prom_graph_error'; errorText.textContent = 'No timeseries returned'; this.graphTd.appendChild(errorText); return; } // Render. var graph = new Rickshaw.Graph({ interpolation: "linear", width: this.graphTd.offsetWidth, height: this.params.height, element: this.graphTd, renderer: this.params.renderer, max: this.params.max, min: this.params.min, series: series }); var hoverDetail = new Rickshaw.Graph.HoverDetail({ graph: graph, onRender: function() { var xLabel = this.element.getElementsByClassName("x_label")[0]; var item = this.element.getElementsByClassName("item")[0]; if (xLabel.offsetWidth + xLabel.offsetLeft + this.element.offsetLeft > graph.element.offsetWidth || item.offsetWidth + item.offsetLeft + this.element.offsetLeft > graph.element.offsetWidth) { xLabel.classList.add("prom_graph_hover_flipped"); item.classList.add("prom_graph_hover_flipped"); } else { xLabel.classList.remove("prom_graph_hover_flipped"); item.classList.remove("prom_graph_hover_flipped"); } }, yFormatter: function(y) { if (y === null) { return ""; } return this.params.yHoverFormatter(y) + this.params.yUnits; }.bind(this) }); var yAxis = new Rickshaw.Graph.Axis.Y({ graph: graph, tickFormat: this.params.yAxisFormatter }); var xAxis = new Rickshaw.Graph.Axis.Time({ graph: graph, }); var legend = new Rickshaw.Graph.Legend({ graph: graph, element: this.legendDiv }); xAxis.render(); yAxis.render(); graph.render(); this.rickshawGraph = graph; }; PromConsole.Graph.prototype._clearGraph = function() { while (this.graphTd.lastChild) { this.graphTd.removeChild(this.graphTd.lastChild); } while (this.legendDiv.lastChild) { this.legendDiv.removeChild(this.legendDiv.lastChild); } this.rickshawGraph = null; }; PromConsole.Graph.prototype._xhrs = []; PromConsole.Graph.prototype.buildQueryUrl = function(expr) { var p = this.params; return PATH_PREFIX + "/api/v1/query_range?query=" + encodeURIComponent(expr) + "&step=" + p.duration / this.graphTd.offsetWidth + "&start=" + (p.endTime - p.duration) + "&end=" + p.endTime; }; PromConsole.Graph.prototype.dispatch = function() { for (var j = 0; j < this._xhrs.length; j++) { this._xhrs[j].abort(); } var all_data = new Array(this.params.expr.length); this._xhrs = new Array(this.params.expr.length); var pending_requests = this.params.expr.length; for (var i = 0; i < this.params.expr.length; ++i) { var endTime = this.params.endTime; var url = this.buildQueryUrl(this.params.expr[i]); var xhr = new XMLHttpRequest(); xhr.open('get', url, true); xhr.responseType = 'json'; xhr.onerror = function(xhr, i) { this._clearGraph(); var errorText = document.createElement("div"); errorText.className = 'prom_graph_error'; errorText.textContent = 'Error loading data'; this.graphTd.appendChild(errorText); console.log('Error loading data for ' + this.params.expr[i]); pending_requests = 0; // onabort gets any aborts. for (var j = 0; j < pending_requests; j++) { this._xhrs[j].abort(); } }.bind(this, xhr, i); xhr.onload = function(xhr, i) { if (pending_requests === 0) { // Got an error before this success. return; } var data = xhr.response; if (typeof data !== "object") { data = JSON.parse(xhr.responseText); } pending_requests -= 1; all_data[i] = data; if (pending_requests === 0) { this._xhrs = []; this._render(all_data); } }.bind(this, xhr, i); xhr.send(); this._xhrs[i] = xhr; } var loadingImg = document.createElement("img"); loadingImg.src = PATH_PREFIX + '/static/img/ajax-loader.gif'; loadingImg.alt = 'Loading...'; loadingImg.className = 'prom_graph_loading'; this.graphTd.appendChild(loadingImg); }; // Substitute the value of 'label' for [[ label ]]. PromConsole._interpolateName = function(name, metric) { var re = /(.*?)\[\[\s*(\w+)+\s*\]\](.*?)/g; var result = ''; while (match = re.exec(name)) { result = result + match[1] + metric[match[2]] + match[3]; } if (!result) { return name; } return result; }; // Given the data returned by the API, return an appropriate function // to return plot names. PromConsole._chooseNameFunction = function(data) { // By default, use the full metric name. var nameFunc = function (metric) { name = metric.__name__ + "{"; for (var label in metric) { if (label.substring(0,2) == "__") { continue; } name += label + "='" + metric[label] + "',"; } return name + "}"; }; // If only one label varies, use that value. var labelValues = {}; for (var e = 0; e < data.length; e++) { for (var i = 0; i < data[e].data.result.length; i++) { for (var label in data[e].data.result[i].metric) { if (!(label in labelValues)) { labelValues[label] = {}; } labelValues[label][data[e].data.result[i].metric[label]] = 1; } } } var multiValueLabels = []; for (var label in labelValues) { if (Object.keys(labelValues[label]).length > 1) { multiValueLabels.push(label); } } if (multiValueLabels.length == 1) { nameFunc = function(metric) { return metric[multiValueLabels[0]]; }; } return nameFunc; }; // Given a list of expressions, produce the /graph url for them. PromConsole._graphsToSlashGraphURL = function(exprs) { var data = []; for (var i = 0; i < exprs.length; ++i) { data.push({'expr': exprs[i], 'tab': 0}); } return PATH_PREFIX + '/graph#' + encodeURIComponent(JSON.stringify(data)); }; prometheus-2.1.0+ds/web/ui/static/js/targets.js000066400000000000000000000035321323116307200214140ustar00rootroot00000000000000function toggleJobTable(button, shouldExpand){ if (button.length === 0) { return; } if (shouldExpand) { button.removeClass("collapsed-table").addClass("expanded-table").html("show less"); } else { button.removeClass("expanded-table").addClass("collapsed-table").html("show more"); } button.parents(".table-container").find("table").toggle(shouldExpand); } function init() { $("button.targets").click(function () { const tableTitle = $(this).closest("h2").find("a").attr("id"); if ($(this).hasClass("collapsed-table")) { localStorage.setItem(tableTitle, "expanded"); toggleJobTable($(this), true); } else if ($(this).hasClass("expanded-table")) { localStorage.setItem(tableTitle, "collapsed"); toggleJobTable($(this), false); } }); $(".job_header a").each(function (_, link) { const cachedTableState = localStorage.getItem($(link).attr("id")); if (cachedTableState === "collapsed") { toggleJobTable($(this).siblings("button"), false); } }); $(".filters button.unhealthy-targets").click(function(e) { const button = $(e.target); const icon = $(e.target).children("i"); if (icon.hasClass("glyphicon-unchecked")) { icon.removeClass("glyphicon-unchecked") .addClass("glyphicon-check btn-primary"); button.addClass("is-checked"); $(".table-container").each(showUnhealthy); } else if (icon.hasClass("glyphicon-check")) { icon.removeClass("glyphicon-check btn-primary") .addClass("glyphicon-unchecked"); button.removeClass("is-checked"); $(".table-container").each(showAll); } }); } function showAll(_, container) { $(container).show(); } function showUnhealthy(_, container) { const isHealthy = $(container).find("h2").attr("class").indexOf("danger") < 0; if (isHealthy) { $(container).hide(); } } $(init); prometheus-2.1.0+ds/web/ui/static/vendor/000077500000000000000000000000001323116307200202635ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/static/vendor/bootstrap3-typeahead/000077500000000000000000000000001323116307200243255ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.js000066400000000000000000000313371323116307200307340ustar00rootroot00000000000000/* ============================================================= * bootstrap3-typeahead.js v3.1.0 * https://github.com/bassjobsen/Bootstrap-3-Typeahead * ============================================================= * Original written by @mdo and @fat * ============================================================= * Copyright 2014 Bass Jobsen @bassjobsen * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================ */ (function (root, factory) { 'use strict'; // CommonJS module is defined if (typeof module !== 'undefined' && module.exports) { module.exports = factory(require('jquery')); } // AMD module is defined else if (typeof define === 'function' && define.amd) { define(['jquery'], function ($) { return factory ($); }); } else { factory(root.jQuery); } }(this, function ($) { 'use strict'; // jshint laxcomma: true /* TYPEAHEAD PUBLIC CLASS DEFINITION * ================================= */ var Typeahead = function (element, options) { this.$element = $(element); this.options = $.extend({}, $.fn.typeahead.defaults, options); this.matcher = this.options.matcher || this.matcher; this.sorter = this.options.sorter || this.sorter; this.select = this.options.select || this.select; this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; this.highlighter = this.options.highlighter || this.highlighter; this.render = this.options.render || this.render; this.updater = this.options.updater || this.updater; this.displayText = this.options.displayText || this.displayText; this.source = this.options.source; this.delay = this.options.delay; this.$menu = $(this.options.menu); this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; this.shown = false; this.listen(); this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus : false; this.afterSelect = this.options.afterSelect; this.addItem = false; }; Typeahead.prototype = { constructor: Typeahead, select: function () { var val = this.$menu.find('.active').data('value'); this.$element.data('active', val); if(this.autoSelect || val) { var newVal = this.updater(val); this.$element .val(this.displayText(newVal) || newVal) .change(); this.afterSelect(newVal); } return this.hide(); }, updater: function (item) { return item; }, setSource: function (source) { this.source = source; }, show: function () { var pos = $.extend({}, this.$element.position(), { height: this.$element[0].offsetHeight }), scrollHeight; scrollHeight = typeof this.options.scrollHeight == 'function' ? this.options.scrollHeight.call() : this.options.scrollHeight; (this.$appendTo ? this.$menu.appendTo(this.$appendTo) : this.$menu.insertAfter(this.$element)) .css({ top: pos.top + pos.height + scrollHeight , left: pos.left }) .show(); this.shown = true; return this; }, hide: function () { this.$menu.hide(); this.shown = false; return this; }, lookup: function (query) { var items; if (typeof(query) != 'undefined' && query !== null) { this.query = query; } else { this.query = this.$element.val() || ''; } if (this.query.length < this.options.minLength) { return this.shown ? this.hide() : this; } var worker = $.proxy(function() { if($.isFunction(this.source)) this.source(this.query, $.proxy(this.process, this)); else if (this.source) { this.process(this.source); } }, this); clearTimeout(this.lookupWorker); this.lookupWorker = setTimeout(worker, this.delay); }, process: function (items) { var that = this; items = $.grep(items, function (item) { return that.matcher(item); }); items = this.sorter(items); if (!items.length && !this.options.addItem) { return this.shown ? this.hide() : this; } if (items.length > 0) { this.$element.data('active', items[0]); } else { this.$element.data('active', null); } // Add item if (this.options.addItem){ items.push(this.options.addItem); } if (this.options.items == 'all') { return this.render(items).show(); } else { return this.render(items.slice(0, this.options.items)).show(); } }, matcher: function (item) { var it = this.displayText(item); return ~it.toLowerCase().indexOf(this.query.toLowerCase()); }, sorter: function (items) { var beginswith = [] , caseSensitive = [] , caseInsensitive = [] , item; while ((item = items.shift())) { var it = this.displayText(item); if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); else if (~it.indexOf(this.query)) caseSensitive.push(item); else caseInsensitive.push(item); } return beginswith.concat(caseSensitive, caseInsensitive); }, highlighter: function (item) { var html = $('
    '); var query = this.query; var i = item.toLowerCase().indexOf(query.toLowerCase()); var len, leftPart, middlePart, rightPart, strong; len = query.length; if(len === 0){ return html.text(item).html(); } while (i > -1) { leftPart = item.substr(0, i); middlePart = item.substr(i, len); rightPart = item.substr(i + len); strong = $('').text(middlePart); html .append(document.createTextNode(leftPart)) .append(strong); item = rightPart; i = item.toLowerCase().indexOf(query.toLowerCase()); } return html.append(document.createTextNode(item)).html(); }, render: function (items) { var that = this; var self = this; var activeFound = false; items = $(items).map(function (i, item) { var text = self.displayText(item); i = $(that.options.item).data('value', item); i.find('a').html(that.highlighter(text)); if (text == self.$element.val()) { i.addClass('active'); self.$element.data('active', item); activeFound = true; } return i[0]; }); if (this.autoSelect && !activeFound) { items.first().addClass('active'); this.$element.data('active', items.first().data('value')); } this.$menu.html(items); return this; }, displayText: function(item) { return item.name || item; }, next: function (event) { var active = this.$menu.find('.active').removeClass('active') , next = active.next(); if (!next.length) { next = $(this.$menu.find('li')[0]); } next.addClass('active'); }, prev: function (event) { var active = this.$menu.find('.active').removeClass('active') , prev = active.prev(); if (!prev.length) { prev = this.$menu.find('li').last(); } prev.addClass('active'); }, listen: function () { this.$element .on('focus', $.proxy(this.focus, this)) .on('blur', $.proxy(this.blur, this)) .on('keypress', $.proxy(this.keypress, this)) .on('keyup', $.proxy(this.keyup, this)); if (this.eventSupported('keydown')) { this.$element.on('keydown', $.proxy(this.keydown, this)); } this.$menu .on('click', $.proxy(this.click, this)) .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) .on('mouseleave', 'li', $.proxy(this.mouseleave, this)); }, destroy : function () { this.$element.data('typeahead',null); this.$element.data('active',null); this.$element .off('focus') .off('blur') .off('keypress') .off('keyup'); if (this.eventSupported('keydown')) { this.$element.off('keydown'); } this.$menu.remove(); }, eventSupported: function(eventName) { var isSupported = eventName in this.$element; if (!isSupported) { this.$element.setAttribute(eventName, 'return;'); isSupported = typeof this.$element[eventName] === 'function'; } return isSupported; }, move: function (e) { if (!this.shown) return; switch(e.keyCode) { case 9: // tab case 13: // enter case 27: // escape e.preventDefault(); break; case 38: // up arrow // with the shiftKey (this is actually the left parenthesis) if (e.shiftKey) return; e.preventDefault(); this.prev(); break; case 40: // down arrow // with the shiftKey (this is actually the right parenthesis) if (e.shiftKey) return; e.preventDefault(); this.next(); break; } e.stopPropagation(); }, keydown: function (e) { this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); if (!this.shown && e.keyCode == 40) { this.lookup(); } else { this.move(e); } }, keypress: function (e) { if (this.suppressKeyPressRepeat) return; this.move(e); }, keyup: function (e) { switch(e.keyCode) { case 40: // down arrow case 38: // up arrow case 16: // shift case 17: // ctrl case 18: // alt break; case 9: // tab case 13: // enter if (!this.shown) return; this.select(); break; case 27: // escape if (!this.shown) return; this.hide(); break; default: this.lookup(); } e.stopPropagation(); e.preventDefault(); }, focus: function (e) { if (!this.focused) { this.focused = true; if (this.options.showHintOnFocus) { this.lookup(''); } } }, blur: function (e) { this.focused = false; if (!this.mousedover && this.shown) this.hide(); }, click: function (e) { e.stopPropagation(); e.preventDefault(); this.select(); this.$element.focus(); }, mouseenter: function (e) { this.mousedover = true; this.$menu.find('.active').removeClass('active'); $(e.currentTarget).addClass('active'); }, mouseleave: function (e) { this.mousedover = false; if (!this.focused && this.shown) this.hide(); } }; /* TYPEAHEAD PLUGIN DEFINITION * =========================== */ var old = $.fn.typeahead; $.fn.typeahead = function (option) { var arg = arguments; if (typeof option == 'string' && option == 'getActive') { return this.data('active'); } return this.each(function () { var $this = $(this) , data = $this.data('typeahead') , options = typeof option == 'object' && option; if (!data) $this.data('typeahead', (data = new Typeahead(this, options))); if (typeof option == 'string') { if (arg.length > 1) { data[option].apply(data, Array.prototype.slice.call(arg ,1)); } else { data[option](); } } }); }; $.fn.typeahead.defaults = { source: [] , items: 8 , menu: '' , item: '
  • ' , minLength: 1 , scrollHeight: 0 , autoSelect: true , afterSelect: $.noop , addItem: false , delay: 0 }; $.fn.typeahead.Constructor = Typeahead; /* TYPEAHEAD NO CONFLICT * =================== */ $.fn.typeahead.noConflict = function () { $.fn.typeahead = old; return this; }; /* TYPEAHEAD DATA-API * ================== */ $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { var $this = $(this); if ($this.data('typeahead')) return; $this.typeahead($this.data()); }); })); prometheus-2.1.0+ds/web/ui/static/vendor/fuzzy/000077500000000000000000000000001323116307200214525ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/static/vendor/fuzzy/fuzzy.js000066400000000000000000000130451323116307200232020ustar00rootroot00000000000000/* * Fuzzy * https://github.com/myork/fuzzy * * Copyright (c) 2012 Matt York * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * A slightly modified version of https://github.com/mattyork/fuzzy/blob/3613086aa40c180ca722aeaf48cef575dc57eb5d/lib/fuzzy.js */ (function() { var root = this; var fuzzy = {}; // Use in node or in browser if (typeof exports !== 'undefined') { module.exports = fuzzy; } else { root.fuzzy = fuzzy; } // Return all elements of `array` that have a fuzzy // match against `pattern`. fuzzy.simpleFilter = function(pattern, array) { return array.filter(function(str) { return fuzzy.test(pattern, str); }); }; // Does `pattern` fuzzy match `str`? fuzzy.test = function(pattern, str) { return fuzzy.match(pattern, str) !== null; }; // If `pattern` matches `str`, wrap each matching character // in `opts.pre` and `opts.post`. If no match, return null fuzzy.match = function(pattern, str, opts, _fromIndex) { opts = opts || {}; var patternIdx = 0 , result = [] , len = str.length , fromIndex = _fromIndex || 0 , totalScore = 0 , currScore = 0 // prefix , pre = opts.pre || '' // suffix , post = opts.post || '' // String to compare against. This might be a lowercase version of the // raw string , compareString = opts.caseSensitive && str || str.toLowerCase() , ch; pattern = opts.caseSensitive && pattern || pattern.toLowerCase(); // If there's an exact match, add pre/post, max out score and skip the lookup if (compareString === pattern) { return { rendered: pre + compareString.split('').join(post+pre) + post, score: Infinity }; } // For each character in the string, either add it to the result // or wrap in template if it's the next string in the pattern for(var idx = 0; idx < len; idx++) { ch = str[idx]; if(idx >= fromIndex && compareString[idx] === pattern[patternIdx]) { ch = pre + ch + post; patternIdx += 1; // consecutive characters should increase the score more than linearly currScore += 1 + currScore; } else { currScore = 0; } totalScore += currScore; result[result.length] = ch; } // return rendered string if we have a match for every char if(patternIdx === pattern.length) { var nextPossible = str.indexOf(pattern[0], str.indexOf(pattern[0], fromIndex) + 1) , candidate; // If possible, try to find a better match at the rest of the string if (nextPossible > -1 && str.length - nextPossible >= pattern.length) { var candidate = fuzzy.match(pattern, str, opts, nextPossible); } return candidate && candidate.score > totalScore ? candidate : { rendered: result.join(''), score: totalScore }; } return null; }; // The normal entry point. Filters `arr` for matches against `pattern`. // It returns an array with matching values of the type: // // [{ // string: 'lah' // The rendered string // , index: 2 // The index of the element in `arr` // , original: 'blah' // The original element in `arr` // }] // // `opts` is an optional argument bag. Details: // // opts = { // // string to put before a matching character // pre: '' // // // string to put after matching character // , post: '' // // // Optional function. Input is an entry in the given arr`, // // output should be the string to test `pattern` against. // // In this example, if `arr = [{crying: 'koala'}]` we would return // // 'koala'. // , extract: function(arg) { return arg.crying; } // } fuzzy.filter = function(pattern, arr, opts) { if(!arr || arr.length === 0) { return []; } if (typeof pattern !== 'string' || pattern === '') { return arr; } opts = opts || {}; return arr .reduce(function(prev, element, idx, arr) { var str = element; if(opts.extract) { str = opts.extract(element); } var rendered = fuzzy.match(pattern, str, opts); if(rendered != null) { prev[prev.length] = { string: rendered.rendered , score: rendered.score , index: idx , original: element }; } return prev; }, []) // Sort by score. Browsers are inconsistent wrt stable/unstable // sorting, so force stable by using the index in the case of tie. // See http://ofb.net/~sethml/is-sort-stable.html .sort(function(a,b) { var compare = b.score - a.score; if(compare) return compare; return a.index - b.index; }); }; }()); prometheus-2.1.0+ds/web/ui/static/vendor/js/000077500000000000000000000000001323116307200206775ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/static/vendor/js/jquery.selection.js000066400000000000000000000311211323116307200245360ustar00rootroot00000000000000/*! * jQuery.selection - jQuery Plugin * * Copyright (c) 2010-2014 IWASAKI Koji (@madapaja). * http://blog.madapaja.net/ * Under The MIT License * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function($, win, doc) { /** * get caret status of the selection of the element * * @param {Element} element target DOM element * @return {Object} return * @return {String} return.text selected text * @return {Number} return.start start position of the selection * @return {Number} return.end end position of the selection */ var _getCaretInfo = function(element){ var res = { text: '', start: 0, end: 0 }; if (!element.value) { /* no value or empty string */ return res; } try { if (win.getSelection) { /* except IE */ res.start = element.selectionStart; res.end = element.selectionEnd; res.text = element.value.slice(res.start, res.end); } else if (doc.selection) { /* for IE */ element.focus(); var range = doc.selection.createRange(), range2 = doc.body.createTextRange(); res.text = range.text; try { range2.moveToElementText(element); range2.setEndPoint('StartToStart', range); } catch (e) { range2 = element.createTextRange(); range2.setEndPoint('StartToStart', range); } res.start = element.value.length - range2.text.length; res.end = res.start + range.text.length; } } catch (e) { /* give up */ } return res; }; /** * caret operation for the element * @type {Object} */ var _CaretOperation = { /** * get caret position * * @param {Element} element target element * @return {Object} return * @return {Number} return.start start position for the selection * @return {Number} return.end end position for the selection */ getPos: function(element) { var tmp = _getCaretInfo(element); return {start: tmp.start, end: tmp.end}; }, /** * set caret position * * @param {Element} element target element * @param {Object} toRange caret position * @param {Number} toRange.start start position for the selection * @param {Number} toRange.end end position for the selection * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" */ setPos: function(element, toRange, caret) { caret = this._caretMode(caret); if (caret === 'start') { toRange.end = toRange.start; } else if (caret === 'end') { toRange.start = toRange.end; } element.focus(); try { if (element.createTextRange) { var range = element.createTextRange(); if (win.navigator.userAgent.toLowerCase().indexOf("msie") >= 0) { toRange.start = element.value.substr(0, toRange.start).replace(/\r/g, '').length; toRange.end = element.value.substr(0, toRange.end).replace(/\r/g, '').length; } range.collapse(true); range.moveStart('character', toRange.start); range.moveEnd('character', toRange.end - toRange.start); range.select(); } else if (element.setSelectionRange) { element.setSelectionRange(toRange.start, toRange.end); } } catch (e) { /* give up */ } }, /** * get selected text * * @param {Element} element target element * @return {String} return selected text */ getText: function(element) { return _getCaretInfo(element).text; }, /** * get caret mode * * @param {String} caret caret mode * @return {String} return any of the following: "keep" | "start" | "end" */ _caretMode: function(caret) { caret = caret || "keep"; if (caret === false) { caret = 'end'; } switch (caret) { case 'keep': case 'start': case 'end': break; default: caret = 'keep'; } return caret; }, /** * replace selected text * * @param {Element} element target element * @param {String} text replacement text * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" */ replace: function(element, text, caret) { var tmp = _getCaretInfo(element), orig = element.value, pos = $(element).scrollTop(), range = {start: tmp.start, end: tmp.start + text.length}; element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.end); $(element).scrollTop(pos); this.setPos(element, range, caret); }, /** * insert before the selected text * * @param {Element} element target element * @param {String} text insertion text * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" */ insertBefore: function(element, text, caret) { var tmp = _getCaretInfo(element), orig = element.value, pos = $(element).scrollTop(), range = {start: tmp.start + text.length, end: tmp.end + text.length}; element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.start); $(element).scrollTop(pos); this.setPos(element, range, caret); }, /** * insert after the selected text * * @param {Element} element target element * @param {String} text insertion text * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" */ insertAfter: function(element, text, caret) { var tmp = _getCaretInfo(element), orig = element.value, pos = $(element).scrollTop(), range = {start: tmp.start, end: tmp.end}; element.value = orig.substr(0, tmp.end) + text + orig.substr(tmp.end); $(element).scrollTop(pos); this.setPos(element, range, caret); } }; /* add jQuery.selection */ $.extend({ /** * get selected text on the window * * @param {String} mode selection mode: any of the following: "text" | "html" * @return {String} return */ selection: function(mode) { var getText = ((mode || 'text').toLowerCase() === 'text'); try { if (win.getSelection) { if (getText) { // get text return win.getSelection().toString(); } else { // get html var sel = win.getSelection(), range; if (sel.getRangeAt) { range = sel.getRangeAt(0); } else { range = doc.createRange(); range.setStart(sel.anchorNode, sel.anchorOffset); range.setEnd(sel.focusNode, sel.focusOffset); } return $('
    ').append(range.cloneContents()).html(); } } else if (doc.selection) { if (getText) { // get text return doc.selection.createRange().text; } else { // get html return doc.selection.createRange().htmlText; } } } catch (e) { /* give up */ } return ''; } }); /* add selection */ $.fn.extend({ selection: function(mode, opts) { opts = opts || {}; switch (mode) { /** * selection('getPos') * get caret position * * @return {Object} return * @return {Number} return.start start position for the selection * @return {Number} return.end end position for the selection */ case 'getPos': return _CaretOperation.getPos(this[0]); /** * selection('setPos', opts) * set caret position * * @param {Number} opts.start start position for the selection * @param {Number} opts.end end position for the selection */ case 'setPos': return this.each(function() { _CaretOperation.setPos(this, opts); }); /** * selection('replace', opts) * replace the selected text * * @param {String} opts.text replacement text * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" */ case 'replace': return this.each(function() { _CaretOperation.replace(this, opts.text, opts.caret); }); /** * selection('insert', opts) * insert before/after the selected text * * @param {String} opts.text insertion text * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" * @param {String} opts.mode insertion mode: any of the following: "before" | "after" */ case 'insert': return this.each(function() { if (opts.mode === 'before') { _CaretOperation.insertBefore(this, opts.text, opts.caret); } else { _CaretOperation.insertAfter(this, opts.text, opts.caret); } }); /** * selection('get') * get selected text * * @return {String} return */ case 'get': /* falls through */ default: return _CaretOperation.getText(this[0]); } return this; } }); })(jQuery, window, window.document); prometheus-2.1.0+ds/web/ui/static/vendor/moment/000077500000000000000000000000001323116307200215625ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/static/vendor/moment/moment-timezone-with-data.js000066400000000000000000005735061323116307200271470ustar00rootroot00000000000000//! moment-timezone.js //! version : 0.5.13 //! Copyright (c) JS Foundation and other contributors //! license : MIT //! github.com/moment/moment-timezone (function (root, factory) { "use strict"; /*global define*/ if (typeof define === 'function' && define.amd) { define(['moment'], factory); // AMD } else if (typeof module === 'object' && module.exports) { module.exports = factory(require('moment')); // Node } else { factory(root.moment); // Browser } }(this, function (moment) { "use strict"; // Do not load moment-timezone a second time. // if (moment.tz !== undefined) { // logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion); // return moment; // } var VERSION = "0.5.13", zones = {}, links = {}, names = {}, guesses = {}, cachedGuess, momentVersion = moment.version.split('.'), major = +momentVersion[0], minor = +momentVersion[1]; // Moment.js version check if (major < 2 || (major === 2 && minor < 6)) { logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com'); } /************************************ Unpacking ************************************/ function charCodeToInt(charCode) { if (charCode > 96) { return charCode - 87; } else if (charCode > 64) { return charCode - 29; } return charCode - 48; } function unpackBase60(string) { var i = 0, parts = string.split('.'), whole = parts[0], fractional = parts[1] || '', multiplier = 1, num, out = 0, sign = 1; // handle negative numbers if (string.charCodeAt(0) === 45) { i = 1; sign = -1; } // handle digits before the decimal for (i; i < whole.length; i++) { num = charCodeToInt(whole.charCodeAt(i)); out = 60 * out + num; } // handle digits after the decimal for (i = 0; i < fractional.length; i++) { multiplier = multiplier / 60; num = charCodeToInt(fractional.charCodeAt(i)); out += num * multiplier; } return out * sign; } function arrayToInt (array) { for (var i = 0; i < array.length; i++) { array[i] = unpackBase60(array[i]); } } function intToUntil (array, length) { for (var i = 0; i < length; i++) { array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds } array[length - 1] = Infinity; } function mapIndices (source, indices) { var out = [], i; for (i = 0; i < indices.length; i++) { out[i] = source[indices[i]]; } return out; } function unpack (string) { var data = string.split('|'), offsets = data[2].split(' '), indices = data[3].split(''), untils = data[4].split(' '); arrayToInt(offsets); arrayToInt(indices); arrayToInt(untils); intToUntil(untils, indices.length); return { name : data[0], abbrs : mapIndices(data[1].split(' '), indices), offsets : mapIndices(offsets, indices), untils : untils, population : data[5] | 0 }; } /************************************ Zone object ************************************/ function Zone (packedString) { if (packedString) { this._set(unpack(packedString)); } } Zone.prototype = { _set : function (unpacked) { this.name = unpacked.name; this.abbrs = unpacked.abbrs; this.untils = unpacked.untils; this.offsets = unpacked.offsets; this.population = unpacked.population; }, _index : function (timestamp) { var target = +timestamp, untils = this.untils, i; for (i = 0; i < untils.length; i++) { if (target < untils[i]) { return i; } } }, parse : function (timestamp) { var target = +timestamp, offsets = this.offsets, untils = this.untils, max = untils.length - 1, offset, offsetNext, offsetPrev, i; for (i = 0; i < max; i++) { offset = offsets[i]; offsetNext = offsets[i + 1]; offsetPrev = offsets[i ? i - 1 : i]; if (offset < offsetNext && tz.moveAmbiguousForward) { offset = offsetNext; } else if (offset > offsetPrev && tz.moveInvalidForward) { offset = offsetPrev; } if (target < untils[i] - (offset * 60000)) { return offsets[i]; } } return offsets[max]; }, abbr : function (mom) { return this.abbrs[this._index(mom)]; }, offset : function (mom) { return this.offsets[this._index(mom)]; } }; /************************************ Current Timezone ************************************/ function OffsetAt(at) { var timeString = at.toTimeString(); var abbr = timeString.match(/\([a-z ]+\)/i); if (abbr && abbr[0]) { // 17:56:31 GMT-0600 (CST) // 17:56:31 GMT-0600 (Central Standard Time) abbr = abbr[0].match(/[A-Z]/g); abbr = abbr ? abbr.join('') : undefined; } else { // 17:56:31 CST // 17:56:31 GMT+0800 (台北標準時間) abbr = timeString.match(/[A-Z]{3,5}/g); abbr = abbr ? abbr[0] : undefined; } if (abbr === 'GMT') { abbr = undefined; } this.at = +at; this.abbr = abbr; this.offset = at.getTimezoneOffset(); } function ZoneScore(zone) { this.zone = zone; this.offsetScore = 0; this.abbrScore = 0; } ZoneScore.prototype.scoreOffsetAt = function (offsetAt) { this.offsetScore += Math.abs(this.zone.offset(offsetAt.at) - offsetAt.offset); if (this.zone.abbr(offsetAt.at).replace(/[^A-Z]/g, '') !== offsetAt.abbr) { this.abbrScore++; } }; function findChange(low, high) { var mid, diff; while ((diff = ((high.at - low.at) / 12e4 | 0) * 6e4)) { mid = new OffsetAt(new Date(low.at + diff)); if (mid.offset === low.offset) { low = mid; } else { high = mid; } } return low; } function userOffsets() { var startYear = new Date().getFullYear() - 2, last = new OffsetAt(new Date(startYear, 0, 1)), offsets = [last], change, next, i; for (i = 1; i < 48; i++) { next = new OffsetAt(new Date(startYear, i, 1)); if (next.offset !== last.offset) { change = findChange(last, next); offsets.push(change); offsets.push(new OffsetAt(new Date(change.at + 6e4))); } last = next; } for (i = 0; i < 4; i++) { offsets.push(new OffsetAt(new Date(startYear + i, 0, 1))); offsets.push(new OffsetAt(new Date(startYear + i, 6, 1))); } return offsets; } function sortZoneScores (a, b) { if (a.offsetScore !== b.offsetScore) { return a.offsetScore - b.offsetScore; } if (a.abbrScore !== b.abbrScore) { return a.abbrScore - b.abbrScore; } return b.zone.population - a.zone.population; } function addToGuesses (name, offsets) { var i, offset; arrayToInt(offsets); for (i = 0; i < offsets.length; i++) { offset = offsets[i]; guesses[offset] = guesses[offset] || {}; guesses[offset][name] = true; } } function guessesForUserOffsets (offsets) { var offsetsLength = offsets.length, filteredGuesses = {}, out = [], i, j, guessesOffset; for (i = 0; i < offsetsLength; i++) { guessesOffset = guesses[offsets[i].offset] || {}; for (j in guessesOffset) { if (guessesOffset.hasOwnProperty(j)) { filteredGuesses[j] = true; } } } for (i in filteredGuesses) { if (filteredGuesses.hasOwnProperty(i)) { out.push(names[i]); } } return out; } function rebuildGuess () { // use Intl API when available and returning valid time zone try { var intlName = Intl.DateTimeFormat().resolvedOptions().timeZone; if (intlName){ var name = names[normalizeName(intlName)]; if (name) { return name; } logError("Moment Timezone found " + intlName + " from the Intl api, but did not have that data loaded."); } } catch (e) { // Intl unavailable, fall back to manual guessing. } var offsets = userOffsets(), offsetsLength = offsets.length, guesses = guessesForUserOffsets(offsets), zoneScores = [], zoneScore, i, j; for (i = 0; i < guesses.length; i++) { zoneScore = new ZoneScore(getZone(guesses[i]), offsetsLength); for (j = 0; j < offsetsLength; j++) { zoneScore.scoreOffsetAt(offsets[j]); } zoneScores.push(zoneScore); } zoneScores.sort(sortZoneScores); return zoneScores.length > 0 ? zoneScores[0].zone.name : undefined; } function guess (ignoreCache) { if (!cachedGuess || ignoreCache) { cachedGuess = rebuildGuess(); } return cachedGuess; } /************************************ Global Methods ************************************/ function normalizeName (name) { return (name || '').toLowerCase().replace(/\//g, '_'); } function addZone (packed) { var i, name, split, normalized; if (typeof packed === "string") { packed = [packed]; } for (i = 0; i < packed.length; i++) { split = packed[i].split('|'); name = split[0]; normalized = normalizeName(name); zones[normalized] = packed[i]; names[normalized] = name; if (split[5]) { addToGuesses(normalized, split[2].split(' ')); } } } function getZone (name, caller) { name = normalizeName(name); var zone = zones[name]; var link; if (zone instanceof Zone) { return zone; } if (typeof zone === 'string') { zone = new Zone(zone); zones[name] = zone; return zone; } // Pass getZone to prevent recursion more than 1 level deep if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) { zone = zones[name] = new Zone(); zone._set(link); zone.name = names[name]; return zone; } return null; } function getNames () { var i, out = []; for (i in names) { if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) { out.push(names[i]); } } return out.sort(); } function addLink (aliases) { var i, alias, normal0, normal1; if (typeof aliases === "string") { aliases = [aliases]; } for (i = 0; i < aliases.length; i++) { alias = aliases[i].split('|'); normal0 = normalizeName(alias[0]); normal1 = normalizeName(alias[1]); links[normal0] = normal1; names[normal0] = alias[0]; links[normal1] = normal0; names[normal1] = alias[1]; } } function loadData (data) { addZone(data.zones); addLink(data.links); tz.dataVersion = data.version; } function zoneExists (name) { if (!zoneExists.didShowError) { zoneExists.didShowError = true; logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')"); } return !!getZone(name); } function needsOffset (m) { return !!(m._a && (m._tzm === undefined)); } function logError (message) { if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(message); } } /************************************ moment.tz namespace ************************************/ function tz (input) { var args = Array.prototype.slice.call(arguments, 0, -1), name = arguments[arguments.length - 1], zone = getZone(name), out = moment.utc.apply(null, args); if (zone && !moment.isMoment(input) && needsOffset(out)) { out.add(zone.parse(out), 'minutes'); } out.tz(name); return out; } tz.version = VERSION; tz.dataVersion = ''; tz._zones = zones; tz._links = links; tz._names = names; tz.add = addZone; tz.link = addLink; tz.load = loadData; tz.zone = getZone; tz.zoneExists = zoneExists; // deprecated in 0.1.0 tz.guess = guess; tz.names = getNames; tz.Zone = Zone; tz.unpack = unpack; tz.unpackBase60 = unpackBase60; tz.needsOffset = needsOffset; tz.moveInvalidForward = true; tz.moveAmbiguousForward = false; /************************************ Interface with Moment.js ************************************/ var fn = moment.fn; moment.tz = tz; moment.defaultZone = null; moment.updateOffset = function (mom, keepTime) { var zone = moment.defaultZone, offset; if (mom._z === undefined) { if (zone && needsOffset(mom) && !mom._isUTC) { mom._d = moment.utc(mom._a)._d; mom.utc().add(zone.parse(mom), 'minutes'); } mom._z = zone; } if (mom._z) { offset = mom._z.offset(mom); if (Math.abs(offset) < 16) { offset = offset / 60; } if (mom.utcOffset !== undefined) { mom.utcOffset(-offset, keepTime); } else { mom.zone(offset, keepTime); } } }; fn.tz = function (name) { if (name) { this._z = getZone(name); if (this._z) { moment.updateOffset(this); } else { logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/."); } return this; } if (this._z) { return this._z.name; } }; function abbrWrap (old) { return function () { if (this._z) { return this._z.abbr(this); } return old.call(this); }; } function resetZoneWrap (old) { return function () { this._z = null; return old.apply(this, arguments); }; } fn.zoneName = abbrWrap(fn.zoneName); fn.zoneAbbr = abbrWrap(fn.zoneAbbr); fn.utc = resetZoneWrap(fn.utc); moment.tz.setDefault = function(name) { if (major < 2 || (major === 2 && minor < 9)) { logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.'); } moment.defaultZone = name ? getZone(name) : null; return moment; }; // Cloning a moment should include the _z property. var momentProperties = moment.momentProperties; if (Object.prototype.toString.call(momentProperties) === '[object Array]') { // moment 2.8.1+ momentProperties.push('_z'); momentProperties.push('_a'); } else if (momentProperties) { // moment 2.7.0 momentProperties._z = null; } loadData({ "version": "2017b", "zones": [ "Africa/Abidjan|LMT GMT|g.8 0|01|-2ldXH.Q|48e5", "Africa/Accra|LMT GMT +0020|.Q 0 -k|012121212121212121212121212121212121212121212121|-26BbX.8 6tzX.8 MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE|41e5", "Africa/Nairobi|LMT EAT +0230 +0245|-2r.g -30 -2u -2J|01231|-1F3Cr.g 3Dzr.g okMu MFXJ|47e5", "Africa/Algiers|PMT WET WEST CET CEST|-9.l 0 -10 -10 -20|0121212121212121343431312123431213|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 DA0 Imo0 rd0 De0 9Xz0 1fb0 1ap0 16K0 2yo0 mEp0 hwL0 jxA0 11A0 dDd0 17b0 11B0 1cN0 2Dy0 1cN0 1fB0 1cL0|26e5", "Africa/Lagos|LMT WAT|-d.A -10|01|-22y0d.A|17e6", "Africa/Bissau|LMT -01 GMT|12.k 10 0|012|-2ldWV.E 2xonV.E|39e4", "Africa/Maputo|LMT CAT|-2a.k -20|01|-2GJea.k|26e5", "Africa/Cairo|EET EEST|-20 -30|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-1bIO0 vb0 1ip0 11z0 1iN0 1nz0 12p0 1pz0 10N0 1pz0 16p0 1jz0 s3d0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1WL0 rd0 1Rz0 wp0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1qL0 Xd0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1ny0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 WL0 1qN0 Rb0 1wp0 On0 1zd0 Lz0 1EN0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0|15e6", "Africa/Casablanca|LMT WET WEST CET|u.k 0 -10 -10|0121212121212121213121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2gMnt.E 130Lt.E rb0 Dd0 dVb0 b6p0 TX0 EoB0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4mn0 SyN0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0 11A0 5A0 e00 17c0 1fA0 1a00 1a00 1fA0 17c0 1io0 14o0 1lc0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1lc0 14o0 1fA0|32e5", "Africa/Ceuta|WET WEST CET CEST|0 -10 -10 -20|010101010101010101010232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-25KN0 11z0 drd0 18p0 3HX0 17d0 1fz0 1a10 1io0 1a00 1y7o0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4VB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|85e3", "Africa/El_Aaiun|LMT -01 WET WEST|Q.M 10 0 -10|01232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1rDz7.c 1GVA7.c 6L0 AL0 1Nd0 XX0 1Cp0 pz0 1cBB0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0 11A0 5A0 e00 17c0 1fA0 1a00 1a00 1fA0 17c0 1io0 14o0 1lc0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1lc0 14o0 1fA0|20e4", "Africa/Johannesburg|SAST SAST SAST|-1u -20 -30|012121|-2GJdu 1Ajdu 1cL0 1cN0 1cL0|84e5", "Africa/Khartoum|LMT CAT CAST EAT|-2a.8 -20 -30 -30|01212121212121212121212121212121213|-1yW2a.8 1zK0a.8 16L0 1iN0 17b0 1jd0 17b0 1ip0 17z0 1i10 17X0 1hB0 18n0 1hd0 19b0 1gp0 19z0 1iN0 17b0 1ip0 17z0 1i10 18n0 1hd0 18L0 1gN0 19b0 1gp0 19z0 1iN0 17z0 1i10 17X0 yGd0|51e5", "Africa/Monrovia|MMT MMT GMT|H.8 I.u 0|012|-23Lzg.Q 28G01.m|11e5", "Africa/Ndjamena|LMT WAT WAST|-10.c -10 -20|0121|-2le10.c 2J3c0.c Wn0|13e5", "Africa/Tripoli|LMT CET CEST EET|-Q.I -10 -20 -20|012121213121212121212121213123123|-21JcQ.I 1hnBQ.I vx0 4iP0 xx0 4eN0 Bb0 7ip0 U0n0 A10 1db0 1cN0 1db0 1dd0 1db0 1eN0 1bb0 1e10 1cL0 1c10 1db0 1dd0 1db0 1cN0 1db0 1q10 fAn0 1ep0 1db0 AKq0 TA0 1o00|11e5", "Africa/Tunis|PMT CET CEST|-9.l -10 -20|0121212121212121212121212121212121|-2nco9.l 18pa9.l 1qM0 DA0 3Tc0 11B0 1ze0 WM0 7z0 3d0 14L0 1cN0 1f90 1ar0 16J0 1gXB0 WM0 1rA0 11c0 nwo0 Ko0 1cM0 1cM0 1rA0 10M0 zuM0 10N0 1aN0 1qM0 WM0 1qM0 11A0 1o00|20e5", "Africa/Windhoek|+0130 SAST SAST CAT WAT WAST|-1u -20 -30 -20 -10 -20|012134545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2GJdu 1Ajdu 1cL0 1SqL0 9NA0 11D0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0|32e4", "America/Adak|NST NWT NPT BST BDT AHST HST HDT|b0 a0 a0 b0 a0 a0 a0 90|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|326", "America/Anchorage|AST AWT APT AHST AHDT YST AKST AKDT|a0 90 90 a0 90 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T00 8wX0 iA0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|30e4", "America/Port_of_Spain|LMT AST|46.4 40|01|-2kNvR.U|43e3", "America/Araguaina|LMT -03 -02|3c.M 30 20|0121212121212121212121212121212121212121212121212121|-2glwL.c HdKL.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 ny10 Lz0|14e4", "America/Argentina/Buenos_Aires|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232323232323232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 A4p0 uL0 1qN0 WL0", "America/Argentina/Catamarca|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232323132321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 rlB0 7B0 8zb0 uL0", "America/Argentina/Cordoba|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232323132323232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 A4p0 uL0 1qN0 WL0", "America/Argentina/Jujuy|CMT -04 -03 -02|4g.M 40 30 20|012121212121212121212121212121212121212121232323121323232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1ze0 TX0 1ld0 WK0 1wp0 TX0 A4p0 uL0", "America/Argentina/La_Rioja|CMT -04 -03 -02|4g.M 40 30 20|012121212121212121212121212121212121212121232323231232321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 rlB0 7B0 8zb0 uL0", "America/Argentina/Mendoza|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232312121321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1u20 SL0 1vd0 Tb0 1wp0 TW0 ri10 Op0 7TX0 uL0", "America/Argentina/Rio_Gallegos|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232323232321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 rlB0 7B0 8zb0 uL0", "America/Argentina/Salta|CMT -04 -03 -02|4g.M 40 30 20|012121212121212121212121212121212121212121232323231323232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 A4p0 uL0", "America/Argentina/San_Juan|CMT -04 -03 -02|4g.M 40 30 20|012121212121212121212121212121212121212121232323231232321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 rld0 m10 8lb0 uL0", "America/Argentina/San_Luis|CMT -04 -03 -02|4g.M 40 30 20|012121212121212121212121212121212121212121232323121212321212|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 XX0 1q20 SL0 AN0 vDb0 m10 8lb0 8L0 jd0 1qN0 WL0 1qN0", "America/Argentina/Tucuman|CMT -04 -03 -02|4g.M 40 30 20|0121212121212121212121212121212121212121212323232313232123232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 rlB0 4N0 8BX0 uL0 1qN0 WL0", "America/Argentina/Ushuaia|CMT -04 -03 -02|4g.M 40 30 20|01212121212121212121212121212121212121212123232323232321232|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 rkN0 8p0 8zb0 uL0", "America/Curacao|LMT -0430 AST|4z.L 4u 40|012|-2kV7o.d 28KLS.d|15e4", "America/Asuncion|AMT -04 -03|3O.E 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-1x589.k 1DKM9.k 3CL0 3Dd0 10L0 1pB0 10n0 1pB0 10n0 1pB0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1lB0 14n0 1dd0 1cL0 1fd0 WL0 1rd0 1aL0 1dB0 Xz0 1qp0 Xb0 1qN0 10L0 1rB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 WN0 1qL0 11B0 1nX0 1ip0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 TX0 1tB0 19X0 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0|28e5", "America/Atikokan|CST CDT CWT CPT EST|60 50 50 50 50|0101234|-25TQ0 1in0 Rnb0 3je0 8x30 iw0|28e2", "America/Bahia|LMT -03 -02|2y.4 30 20|01212121212121212121212121212121212121212121212121212121212121|-2glxp.U HdLp.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 l5B0 Rb0|27e5", "America/Bahia_Banderas|LMT MST CST PST MDT CDT|71 70 60 80 60 50|0121212131414141414141414141414141414152525252525252525252525252525252525252525252525252525252|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|84e3", "America/Barbados|LMT BMT AST ADT|3W.t 3W.t 40 30|01232323232|-1Q0I1.v jsM0 1ODC1.v IL0 1ip0 17b0 1ip0 17b0 1ld0 13b0|28e4", "America/Belem|LMT -03 -02|3d.U 30 20|012121212121212121212121212121|-2glwK.4 HdKK.4 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0|20e5", "America/Belize|LMT CST -0530 CDT|5Q.M 60 5u 50|01212121212121212121212121212121212121212121212121213131|-2kBu7.c fPA7.c Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1f0Mu qn0 lxB0 mn0|57e3", "America/Blanc-Sablon|AST ADT AWT APT|40 30 30 30|010230|-25TS0 1in0 UGp0 8x50 iu0|11e2", "America/Boa_Vista|LMT -04 -03|42.E 40 30|0121212121212121212121212121212121|-2glvV.k HdKV.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 smp0 WL0 1tB0 2L0|62e2", "America/Bogota|BMT -05 -04|4U.g 50 40|0121|-2eb73.I 38yo3.I 2en0|90e5", "America/Boise|PST PDT MST MWT MPT MDT|80 70 70 60 60 60|0101023425252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-261q0 1nX0 11B0 1nX0 8C10 JCL0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 Dd0 1Kn0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e4", "America/Cambridge_Bay|-00 MST MWT MPT MDDT MDT CST CDT EST|0 70 60 60 50 60 60 50 50|0123141515151515151515151515151515151515151515678651515151515151515151515151515151515151515151515151515151515151515151515151|-21Jc0 RO90 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11A0 1nX0 2K0 WQ0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e2", "America/Campo_Grande|LMT -04 -03|3C.s 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwl.w HdLl.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|77e4", "America/Cancun|LMT CST EST EDT CDT|5L.4 60 50 40 50|0123232341414141414141414141414141414141412|-1UQG0 2q2o0 yLB0 1lb0 14p0 1lb0 14p0 Lz0 xB0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4", "America/Caracas|CMT -0430 -04|4r.E 4u 40|01212|-2kV7w.k 28KM2.k 1IwOu kqo0|29e5", "America/Cayenne|LMT -04 -03|3t.k 40 30|012|-2mrwu.E 2gWou.E|58e3", "America/Panama|CMT EST|5j.A 50|01|-2uduE.o|15e5", "America/Chicago|CST CDT EST CWT CPT|60 50 50 50 50|01010101010101010101010101010101010102010101010103401010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 1wp0 TX0 WN0 1qL0 1cN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 11B0 1Hz0 14p0 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|92e5", "America/Chihuahua|LMT MST CST CDT MDT|74.k 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|81e4", "America/Costa_Rica|SJMT CST CDT|5A.d 60 50|0121212121|-1Xd6n.L 2lu0n.L Db0 1Kp0 Db0 pRB0 15b0 1kp0 mL0|12e5", "America/Creston|MST PST|70 80|010|-29DR0 43B0|53e2", "America/Cuiaba|LMT -04 -03|3I.k 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwf.E HdLf.E 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 4a10 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|54e4", "America/Danmarkshavn|LMT -03 -02 GMT|1e.E 30 20 0|01212121212121212121212121212121213|-2a5WJ.k 2z5fJ.k 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 DC0|8", "America/Dawson|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 jrA0 fNd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|13e2", "America/Dawson_Creek|PST PDT PWT PPT MST|80 70 70 70 70|0102301010101010101010101010101010101010101010101010101014|-25TO0 1in0 UGp0 8x10 iy0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 ML0|12e3", "America/Denver|MST MDT MWT MPT|70 60 60 60|01010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 11B0 1qL0 WN0 mn0 Ord0 8x20 ix0 LCN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e5", "America/Detroit|LMT CST EST EWT EPT EDT|5w.b 60 50 40 40 40|01234252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2Cgir.N peqr.N 156L0 8x40 iv0 6fd0 11z0 Jy10 SL0 dnB0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e5", "America/Edmonton|LMT MST MDT MWT MPT|7x.Q 70 60 60 60|01212121212121341212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2yd4q.8 shdq.8 1in0 17d0 hz0 2dB0 1fz0 1a10 11z0 1qN0 WL0 1qN0 11z0 IGN0 8x20 ix0 3NB0 11z0 LFB0 1cL0 3Cp0 1cL0 66N0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|10e5", "America/Eirunepe|LMT -05 -04|4D.s 50 40|0121212121212121212121212121212121|-2glvk.w HdLk.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0 yTd0 d5X0|31e3", "America/El_Salvador|LMT CST CDT|5U.M 60 50|012121|-1XiG3.c 2Fvc3.c WL0 1qN0 WL0|11e5", "America/Tijuana|LMT MST PST PDT PWT PPT|7M.4 70 80 70 70 70|012123245232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQE0 4PX0 8mM0 8lc0 SN0 1cL0 pHB0 83r0 zI0 5O10 1Rz0 cOO0 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 BUp0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|20e5", "America/Fort_Nelson|PST PDT PWT PPT MST|80 70 70 70 70|01023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010104|-25TO0 1in0 UGp0 8x10 iy0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2", "America/Fort_Wayne|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010101023010101010101010101040454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 QI10 Db0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 5Tz0 1o10 qLb0 1cL0 1cN0 1cL0 1qhd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Fortaleza|LMT -03 -02|2y 30 20|0121212121212121212121212121212121212121|-2glxq HdLq 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 5z0 2mN0 On0|34e5", "America/Glace_Bay|LMT AST ADT AWT APT|3X.M 40 30 30 30|012134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsI0.c CwO0.c 1in0 UGp0 8x50 iu0 iq10 11z0 Jg10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|19e3", "America/Godthab|LMT -03 -02|3q.U 30 20|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5Ux.4 2z5dx.4 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e3", "America/Goose_Bay|NST NDT NST NDT NWT NPT AST ADT ADDT|3u.Q 2u.Q 3u 2u 2u 2u 40 30 20|010232323232323245232323232323232323232323232323232323232326767676767676767676767676767676767676767676768676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-25TSt.8 1in0 DXb0 2HbX.8 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 S10 g0u 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|76e2", "America/Grand_Turk|KMT EST EDT AST|57.b 50 40 40|0121212121212121212121212121212121212121212121212121212121212121212121212123|-2l1uQ.N 2HHBQ.N 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e2", "America/Guatemala|LMT CST CDT|62.4 60 50|0121212121|-24KhV.U 2efXV.U An0 mtd0 Nz0 ifB0 17b0 zDB0 11z0|13e5", "America/Guayaquil|QMT -05 -04|5e 50 40|0121|-1yVSK 2uILK rz0|27e5", "America/Guyana|LMT -0345 -03 -04|3Q.E 3J 30 40|0123|-2dvU7.k 2r6LQ.k Bxbf|80e4", "America/Halifax|LMT AST ADT AWT APT|4e.o 40 30 30 30|0121212121212121212121212121212121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsHJ.A xzzJ.A 1db0 3I30 1in0 3HX0 IL0 1E10 ML0 1yN0 Pb0 1Bd0 Mn0 1Bd0 Rz0 1w10 Xb0 1w10 LX0 1w10 Xb0 1w10 Lz0 1C10 Jz0 1E10 OL0 1yN0 Un0 1qp0 Xb0 1qp0 11X0 1w10 Lz0 1HB0 LX0 1C10 FX0 1w10 Xb0 1qp0 Xb0 1BB0 LX0 1td0 Xb0 1qp0 Xb0 Rf0 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 6i10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|39e4", "America/Havana|HMT CST CDT|5t.A 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Meuu.o 72zu.o ML0 sld0 An0 1Nd0 Db0 1Nd0 An0 6Ep0 An0 1Nd0 An0 JDd0 Mn0 1Ap0 On0 1fd0 11X0 1qN0 WL0 1wp0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 14n0 1ld0 14L0 1kN0 15b0 1kp0 1cL0 1cN0 1fz0 1a10 1fz0 1fB0 11z0 14p0 1nX0 11B0 1nX0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 1a10 1in0 1a10 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 17c0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 11A0 6i00 Rc0 1wo0 U00 1tA0 Rc0 1wo0 U00 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0|21e5", "America/Hermosillo|LMT MST CST PST MDT|7n.Q 70 60 80 60|0121212131414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0|64e4", "America/Indiana/Knox|CST CDT CWT CPT EST|60 50 50 50 50|0101023010101010101010101010101010101040101010101010101010101010101010101010101010101010141010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 3Cn0 8wp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 z8o0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Marengo|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010104545454545414545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 dyN0 11z0 6fd0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1e6p0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Petersburg|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010104010101010101010101010141014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 njX0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 3Fb0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 19co0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Tell_City|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Vevay|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010102304545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 kPB0 Awn0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1lnd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Vincennes|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Indiana/Winamac|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010101010454541054545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1za0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Inuvik|-00 PST PDDT MST MDT|0 80 60 70 60|0121343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-FnA0 tWU0 1fA0 wPe0 2pz0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|35e2", "America/Iqaluit|-00 EWT EPT EST EDDT EDT CST CDT|0 40 40 50 30 40 60 50|01234353535353535353535353535353535353535353567353535353535353535353535353535353535353535353535353535353535353535353535353|-16K00 7nX0 iv0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|67e2", "America/Jamaica|KMT EST EDT|57.b 50 40|0121212121212121212121|-2l1uQ.N 2uM1Q.N 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0|94e4", "America/Juneau|PST PWT PPT PDT YDT YST AKST AKDT|80 70 70 70 80 90 90 80|01203030303030303030303030403030356767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cM0 1cM0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|33e3", "America/Kentucky/Louisville|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101010102301010101010101010101010101454545454545414545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 3Fd0 Nb0 LPd0 11z0 RB0 8x30 iw0 Bb0 10N0 2bB0 8in0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 xz0 gso0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Kentucky/Monticello|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 SWp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/La_Paz|CMT BOST -04|4w.A 3w.A 40|012|-1x37r.o 13b0|19e5", "America/Lima|LMT -05 -04|58.A 50 40|0121212121212121|-2tyGP.o 1bDzP.o zX0 1aN0 1cL0 1cN0 1cL0 1PrB0 zX0 1O10 zX0 6Gp0 zX0 98p0 zX0|11e6", "America/Los_Angeles|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 5Wp1 1VaX 3dA0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e6", "America/Maceio|LMT -03 -02|2m.Q 30 20|012121212121212121212121212121212121212121|-2glxB.8 HdLB.8 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 8Q10 WL0 1tB0 5z0 2mN0 On0|93e4", "America/Managua|MMT CST EST CDT|5J.c 60 50 50|0121313121213131|-1quie.M 1yAMe.M 4mn0 9Up0 Dz0 1K10 Dz0 s3F0 1KH0 DB0 9In0 k8p0 19X0 1o30 11y0|22e5", "America/Manaus|LMT -04 -03|40.4 40 30|01212121212121212121212121212121|-2glvX.U HdKX.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0|19e5", "America/Martinique|FFMT AST ADT|44.k 40 30|0121|-2mPTT.E 2LPbT.E 19X0|39e4", "America/Matamoros|LMT CST CDT|6E 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|45e4", "America/Mazatlan|LMT MST CST PST MDT|75.E 70 60 80 60|0121212131414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|44e4", "America/Menominee|CST CDT CWT CPT EST|60 50 50 50 50|01010230101041010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 LCN0 1fz0 6410 9Jb0 1cM0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|85e2", "America/Merida|LMT CST EST CDT|5W.s 60 50 50|0121313131313131313131313131313131313131313131313131313131313131313131313131313131313131|-1UQG0 2q2o0 2hz0 wu30 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|11e5", "America/Metlakatla|PST PWT PPT PDT AKST AKDT|80 70 70 70 90 80|0120303030303030303030303030303030454545454545454545454545454545454545454545454|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1hU10 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2", "America/Mexico_City|LMT MST CST CDT CWT|6A.A 70 60 50 50|012121232324232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 gEn0 TX0 3xd0 Jb0 6zB0 SL0 e5d0 17b0 1Pff0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|20e6", "America/Miquelon|LMT AST -03 -02|3I.E 40 30 20|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2mKkf.k 2LTAf.k gQ10 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|61e2", "America/Moncton|EST AST ADT AWT APT|50 40 30 30 30|012121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsH0 CwN0 1in0 zAo0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1K10 Lz0 1zB0 NX0 1u10 Wn0 S20 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14n1 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 ReX 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|64e3", "America/Monterrey|LMT CST CDT|6F.g 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|41e5", "America/Montevideo|MMT -0330 -03 -02 -0230|3I.I 3u 30 20 2u|012121212121212121212121213232323232324242423243232323232323232323232323232323232323232|-20UIf.g 8jzJ.g 1cLu 1dcu 1cLu 1dcu 1cLu ircu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu WLu 1qMu WLu 1qMu 11zu 1o0u 11zu NAu 11bu 2iMu zWu Dq10 19X0 pd0 jz0 cm10 19X0 1fB0 1on0 11d0 1oL0 1nB0 1fzu 1aou 1fzu 1aou 1fzu 3nAu Jb0 3MN0 1SLu 4jzu 2PB0 Lb0 3Dd0 1pb0 ixd0 An0 1MN0 An0 1wp0 On0 1wp0 Rb0 1zd0 On0 1wp0 Rb0 s8p0 1fB0 1ip0 11z0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0|17e5", "America/Toronto|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101012301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 11Wu 1nzu 1fD0 WJ0 1wr0 Nb0 1Ap0 On0 1zd0 On0 1wp0 TX0 1tB0 TX0 1tB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 4kM0 8x40 iv0 1o10 11z0 1nX0 11z0 1o10 11z0 1o10 1qL0 11D0 1nX0 11B0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|65e5", "America/Nassau|LMT EST EDT|59.u 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2kNuO.u 26XdO.u 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|24e4", "America/New_York|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 11B0 1qL0 1a10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x40 iv0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6", "America/Nipigon|EST EDT EWT EPT|50 40 40 40|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 Rnb0 3je0 8x40 iv0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|16e2", "America/Nome|NST NWT NPT BST BDT YST AKST AKDT|b0 a0 a0 b0 a0 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cl0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|38e2", "America/Noronha|LMT -02 -01|29.E 20 10|0121212121212121212121212121212121212121|-2glxO.k HdKO.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0|30e2", "America/North_Dakota/Beulah|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/North_Dakota/Center|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/North_Dakota/New_Salem|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "America/Ojinaga|LMT MST CST CDT MDT|6V.E 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e3", "America/Pangnirtung|-00 AST AWT APT ADDT ADT EDT EST CST CDT|0 40 30 30 20 30 40 50 60 50|012314151515151515151515151515151515167676767689767676767676767676767676767676767676767676767676767676767676767676767676767|-1XiM0 PnG0 8x50 iu0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1o00 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2", "America/Paramaribo|LMT PMT PMT -0330 -03|3E.E 3E.Q 3E.A 3u 30|01234|-2nDUj.k Wqo0.c qanX.I 1yVXN.o|24e4", "America/Phoenix|MST MDT MWT|70 60 60|01010202010|-261r0 1nX0 11B0 1nX0 SgN0 4Al1 Ap0 1db0 SWqX 1cL0|42e5", "America/Port-au-Prince|PPMT EST EDT|4N 50 40|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-28RHb 2FnMb 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14q0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 i6n0 1nX0 11B0 1nX0 d430 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 3iN0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5", "America/Rio_Branco|LMT -05 -04|4v.c 50 40|01212121212121212121212121212121|-2glvs.M HdLs.M 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0 d5X0|31e4", "America/Porto_Velho|LMT -04 -03|4f.A 40 30|012121212121212121212121212121|-2glvI.o HdKI.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0|37e4", "America/Puerto_Rico|AST AWT APT|40 30 30|0120|-17lU0 7XT0 iu0|24e5", "America/Punta_Arenas|SMT -05 -04 -03|4G.K 50 40 30|0102021212121212121232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-2q2jh.e fJAh.e 5knG.K 1Vzh.e jRAG.K 1pbh.e 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 nHX0 op0 blz0 ko0 Qeo0 WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0", "America/Rainy_River|CST CDT CWT CPT|60 50 50 50|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TQ0 1in0 Rnb0 3je0 8x30 iw0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|842", "America/Rankin_Inlet|-00 CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313131313131313131313131313131313131313131313131313131313131313131|-vDc0 keu0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e2", "America/Recife|LMT -03 -02|2j.A 30 20|0121212121212121212121212121212121212121|-2glxE.o HdLE.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0|33e5", "America/Regina|LMT MST MDT MWT MPT CST|6W.A 70 60 60 60 60|012121212121212121212121341212121212121212121212121215|-2AD51.o uHe1.o 1in0 s2L0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 66N0 1cL0 1cN0 19X0 1fB0 1cL0 1fB0 1cL0 1cN0 1cL0 M30 8x20 ix0 1ip0 1cL0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 3NB0 1cL0 1cN0|19e4", "America/Resolute|-00 CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313431313131313131313131313131313131313131313131313131313131313131|-SnA0 GWS0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|229", "America/Santarem|LMT -04 -03|3C.M 40 30|0121212121212121212121212121212|-2glwl.c HdLl.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0|21e4", "America/Santiago|SMT -05 -04 -03|4G.K 50 40 30|010202121212121212321232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-2q2jh.e fJAh.e 5knG.K 1Vzh.e jRAG.K 1pbh.e 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 nHX0 op0 9Bz0 jb0 1oN0 ko0 Qeo0 WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|62e5", "America/Santo_Domingo|SDMT EST EDT -0430 AST|4E 50 40 4u 40|01213131313131414|-1ttjk 1lJMk Mn0 6sp0 Lbu 1Cou yLu 1RAu wLu 1QMu xzu 1Q0u xXu 1PAu 13jB0 e00|29e5", "America/Sao_Paulo|LMT -03 -02|36.s 30 20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwR.w HdKR.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 pTd0 PX0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|20e6", "America/Scoresbysund|LMT -02 -01 +00|1r.Q 20 10 0|0121323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2a5Ww.8 2z5ew.8 1a00 1cK0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|452", "America/Sitka|PST PWT PPT PDT YST AKST AKDT|80 70 70 70 90 90 80|01203030303030303030303030303030345656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|90e2", "America/St_Johns|NST NDT NST NDT NWT NPT NDDT|3u.Q 2u.Q 3u 2u 2u 2u 1u|01010101010101010101010101010101010102323232323232324523232323232323232323232323232323232323232323232323232323232323232323232323232323232326232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-28oit.8 14L0 1nB0 1in0 1gm0 Dz0 1JB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1fB0 19X0 1fB0 19X0 10O0 eKX.8 19X0 1iq0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4", "America/Swift_Current|LMT MST MDT MWT MPT CST|7b.k 70 60 60 60 60|012134121212121212121215|-2AD4M.E uHdM.E 1in0 UGp0 8x20 ix0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 isN0 1cL0 3Cp0 1cL0 1cN0 11z0 1qN0 WL0 pMp0|16e3", "America/Tegucigalpa|LMT CST CDT|5M.Q 60 50|01212121|-1WGGb.8 2ETcb.8 WL0 1qN0 WL0 GRd0 AL0|11e5", "America/Thule|LMT AST ADT|4z.8 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5To.Q 31NBo.Q 1cL0 1cN0 1cL0 1fB0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|656", "America/Thunder_Bay|CST EST EWT EPT EDT|60 50 40 40 40|0123141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-2q5S0 1iaN0 8x40 iv0 XNB0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4", "America/Vancouver|PST PDT PWT PPT|80 70 70 70|0102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TO0 1in0 UGp0 8x10 iy0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5", "America/Whitehorse|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 3NA0 vrd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e3", "America/Winnipeg|CST CDT CWT CPT|60 50 50 50|010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aIi0 WL0 3ND0 1in0 Jap0 Rb0 aCN0 8x30 iw0 1tB0 11z0 1ip0 11z0 1o10 11z0 1o10 11z0 1rd0 10L0 1op0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 1cL0 1cN0 11z0 6i10 WL0 6i10 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|66e4", "America/Yakutat|YST YWT YPT YDT AKST AKDT|90 80 80 80 90 80|01203030303030303030303030303030304545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-17T10 8x00 iz0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cn0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|642", "America/Yellowknife|-00 MST MWT MPT MDDT MDT|0 70 60 60 50 60|012314151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151|-1pdA0 hix0 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|19e3", "Antarctica/Casey|-00 +08 +11|0 -80 -b0|0121212|-2q00 1DjS0 T90 40P0 KL0 blz0|10", "Antarctica/Davis|-00 +07 +05|0 -70 -50|01012121|-vyo0 iXt0 alj0 1D7v0 VB0 3Wn0 KN0|70", "Antarctica/DumontDUrville|-00 +10|0 -a0|0101|-U0o0 cfq0 bFm0|80", "Antarctica/Macquarie|AEST AEDT -00 +11|-a0 -b0 0 -b0|0102010101010101010101010101010101010101010101010101010101010101010101010101010101010101013|-29E80 19X0 4SL0 1ayy0 Lvs0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0|1", "Antarctica/Mawson|-00 +06 +05|0 -60 -50|012|-CEo0 2fyk0|60", "Pacific/Auckland|NZMT NZST NZST NZDT|-bu -cu -c0 -d0|01020202020202020202020202023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-1GCVu Lz0 1tB0 11zu 1o0u 11zu 1o0u 11zu 1o0u 14nu 1lcu 14nu 1lcu 1lbu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1qLu WMu 1qLu 11Au 1n1bu IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|14e5", "Antarctica/Palmer|-00 -03 -04 -02|0 30 40 20|0121212121213121212121212121212121212121212121212121212121212121212121212121212121|-cao0 nD0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 jsN0 14N0 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0|40", "Antarctica/Rothera|-00 -03|0 30|01|gOo0|130", "Antarctica/Syowa|-00 +03|0 -30|01|-vs00|20", "Antarctica/Troll|-00 +00 +02|0 0 -20|01212121212121212121212121212121212121212121212121212121212121212121|1puo0 hd0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|40", "Antarctica/Vostok|-00 +06|0 -60|01|-tjA0|25", "Europe/Oslo|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2awM0 Qm0 W6o0 5pf0 WM0 1fA0 1cM0 1cM0 1cM0 1cM0 wJc0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1qM0 WM0 zpc0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|62e4", "Asia/Riyadh|LMT +03|-36.Q -30|01|-TvD6.Q|57e5", "Asia/Almaty|LMT +05 +06 +07|-57.M -50 -60 -70|012323232323232323232321232323232323232323232323232|-1Pc57.M eUo7.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0|15e5", "Asia/Amman|LMT EET EEST|-2n.I -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1yW2n.I 1HiMn.I KL0 1oN0 11b0 1oN0 11b0 1pd0 1dz0 1cp0 11b0 1op0 11b0 fO10 1db0 1e10 1cL0 1cN0 1cL0 1cN0 1fz0 1pd0 10n0 1ld0 14n0 1hB0 15b0 1ip0 19X0 1cN0 1cL0 1cN0 17b0 1ld0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1So0 y00 1fc0 1dc0 1co0 1dc0 1cM0 1cM0 1cM0 1o00 11A0 1lc0 17c0 1cM0 1cM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|25e5", "Asia/Anadyr|LMT +12 +13 +14 +11|-bN.U -c0 -d0 -e0 -b0|01232121212121212121214121212121212121212121212121212121212141|-1PcbN.U eUnN.U 23CL0 1db0 2q10 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 2sp0 WM0|13e3", "Asia/Aqtau|LMT +04 +05 +06|-3l.4 -40 -50 -60|012323232323232323232123232312121212121212121212|-1Pc3l.4 eUnl.4 24PX0 2pX0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|15e4", "Asia/Aqtobe|LMT +04 +05 +06|-3M.E -40 -50 -60|0123232323232323232321232323232323232323232323232|-1Pc3M.E eUnM.E 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0|27e4", "Asia/Ashgabat|LMT +04 +05 +06|-3R.w -40 -50 -60|0123232323232323232323212|-1Pc3R.w eUnR.w 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0|41e4", "Asia/Atyrau|LMT +03 +05 +06 +04|-3r.I -30 -50 -60 -40|01232323232323232323242323232323232324242424242|-1Pc3r.I eUor.I 24PW0 2pX0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 2sp0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0", "Asia/Baghdad|BMT +03 +04|-2V.A -30 -40|012121212121212121212121212121212121212121212121212121|-26BeV.A 2ACnV.A 11b0 1cp0 1dz0 1dd0 1db0 1cN0 1cp0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1de0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0|66e5", "Asia/Qatar|LMT +04 +03|-3q.8 -40 -30|012|-21Jfq.8 27BXq.8|96e4", "Asia/Baku|LMT +03 +04 +05|-3j.o -30 -40 -50|01232323232323232323232123232323232323232323232323232323232323232|-1Pc3j.o 1jUoj.o WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 1cM0 9Je0 1o00 11z0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5", "Asia/Bangkok|BMT +07|-6G.4 -70|01|-218SG.4|15e6", "Asia/Barnaul|LMT +06 +07 +08|-5z -60 -70 -80|0123232323232323232323212323232321212121212121212121212121212121212|-21S5z pCnz 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 p90 LE0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0", "Asia/Beirut|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-21aq0 1on0 1410 1db0 19B0 1in0 1ip0 WL0 1lQp0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 q6N0 En0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1op0 11b0 dA10 17b0 1iN0 17b0 1iN0 17b0 1iN0 17b0 1vB0 SL0 1mp0 13z0 1iN0 17b0 1iN0 17b0 1jd0 12n0 1a10 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0|22e5", "Asia/Bishkek|LMT +05 +06 +07|-4W.o -50 -60 -70|012323232323232323232321212121212121212121212121212|-1Pc4W.o eUnW.o 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2e00 1tX0 17b0 1ip0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1cPu 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0|87e4", "Asia/Brunei|LMT +0730 +08|-7D.E -7u -80|012|-1KITD.E gDc9.E|42e4", "Asia/Kolkata|HMT +0630 IST|-5R.k -6u -5u|01212|-18LFR.k 1unn.k HB0 7zX0|15e6", "Asia/Chita|LMT +08 +09 +10|-7x.Q -80 -90 -a0|012323232323232323232321232323232323232323232323232323232323232312|-21Q7x.Q pAnx.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3re0|33e4", "Asia/Choibalsan|LMT +07 +08 +10 +09|-7C -70 -80 -a0 -90|0123434343434343434343434343434343434343434343424242|-2APHC 2UkoC cKn0 1da0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 3Db0 h1f0 1cJ0 1cP0 1cJ0|38e3", "Asia/Shanghai|CST CDT|-80 -90|01010101010101010|-1c1I0 LX0 16p0 1jz0 1Myp0 Rb0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0|23e6", "Asia/Colombo|MMT +0530 +06 +0630|-5j.w -5u -60 -6u|01231321|-2zOtj.w 1rFbN.w 1zzu 7Apu 23dz0 11zu n3cu|22e5", "Asia/Dhaka|HMT +0630 +0530 +06 +07|-5R.k -6u -5u -60 -70|0121343|-18LFR.k 1unn.k HB0 m6n0 2kxbu 1i00|16e6", "Asia/Damascus|LMT EET EEST|-2p.c -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-21Jep.c Hep.c 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1xRB0 11X0 1oN0 10L0 1pB0 11b0 1oN0 10L0 1mp0 13X0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 Nb0 1AN0 Nb0 bcp0 19X0 1gp0 19X0 3ld0 1xX0 Vd0 1Bz0 Sp0 1vX0 10p0 1dz0 1cN0 1cL0 1db0 1db0 1g10 1an0 1ap0 1db0 1fd0 1db0 1cN0 1db0 1dd0 1db0 1cp0 1dz0 1c10 1dX0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 19z0 1fB0 1qL0 11B0 1on0 Wp0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0|26e5", "Asia/Dili|LMT +08 +09|-8m.k -80 -90|01212|-2le8m.k 1dnXm.k 1nfA0 Xld0|19e4", "Asia/Dubai|LMT +04|-3F.c -40|01|-21JfF.c|39e5", "Asia/Dushanbe|LMT +05 +06 +07|-4z.c -50 -60 -70|012323232323232323232321|-1Pc4z.c eUnz.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2hB0|76e4", "Asia/Famagusta|LMT EET EEST +03|-2f.M -20 -30 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212123|-1Vc2f.M 2a3cf.M 1cL0 1qp0 Xz0 19B0 19X0 1fB0 1db0 1cp0 1cL0 1fB0 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1o30 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 15U0", "Asia/Gaza|EET EEST IST IDT|-20 -30 -20 -30|010101010101010101010101010101012323232323232323232323232320101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 11z0 1o10 14o0 1lA1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0|18e5", "Asia/Hebron|EET EEST IST IDT|-20 -30 -20 -30|01010101010101010101010101010101232323232323232323232323232010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 12L0 1mN0 14o0 1lc0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0|25e4", "Asia/Ho_Chi_Minh|LMT PLMT +07 +08 +09|-76.E -76.u -70 -80 -90|0123423232|-2yC76.E bK00.a 1h7b6.u 5lz0 18o0 3Oq0 k5b0 aW00 BAM0|90e5", "Asia/Hong_Kong|LMT HKT HKST JST|-7A.G -80 -90 -90|0121312121212121212121212121212121212121212121212121212121212121212121|-2CFHA.G 1sEP6.G 1cL0 ylu 93X0 1qQu 1tX0 Rd0 1In0 NB0 1cL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1kL0 14N0 1nX0 U10 1tz0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 Rd0 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 17d0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1cL0 6fd0 14n0|73e5", "Asia/Hovd|LMT +06 +07 +08|-66.A -60 -70 -80|012323232323232323232323232323232323232323232323232|-2APG6.A 2Uko6.A cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0|81e3", "Asia/Irkutsk|IMT +07 +08 +09|-6V.5 -70 -80 -90|01232323232323232323232123232323232323232323232323232323232323232|-21zGV.5 pjXV.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|60e4", "Europe/Istanbul|IMT EET EEST +04 +03|-1U.U -20 -30 -40 -30|012121212121212121212121212121212121212121212121212121234343434342121212121212121212121212121212121212121212121212121212121212124|-2ogNU.U dzzU.U 11b0 8tB0 1on0 1410 1db0 19B0 1in0 3Rd0 Un0 1oN0 11b0 zSp0 CL0 mN0 1Vz0 1gN0 1pz0 5Rd0 1fz0 1yp0 ML0 1kp0 17b0 1ip0 17b0 1fB0 19X0 1jB0 18L0 1ip0 17z0 qdd0 xX0 3S10 Tz0 dA10 11z0 1o10 11z0 1qN0 11z0 1ze0 11B0 WM0 1qO0 WI0 1nX0 1rB0 10L0 11B0 1in0 17d0 1in0 2pX0 19E0 1fU0 16Q0 1iI0 16Q0 1iI0 1Vd0 pb0 3Kp0 14o0 1de0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1a00 1fA0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WO0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 15w0|13e6", "Asia/Jakarta|BMT +0720 +0730 +09 +08 WIB|-77.c -7k -7u -90 -80 -70|01232425|-1Q0Tk luM0 mPzO 8vWu 6kpu 4PXu xhcu|31e6", "Asia/Jayapura|LMT +09 +0930 WIT|-9m.M -90 -9u -90|0123|-1uu9m.M sMMm.M L4nu|26e4", "Asia/Jerusalem|JMT IST IDT IDDT|-2k.E -20 -30 -40|01212121212132121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-26Bek.E SyMk.E 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 3LB0 Em0 or0 1cn0 1dB0 16n0 10O0 1ja0 1tC0 14o0 1cM0 1a00 11A0 1Na0 An0 1MP0 AJ0 1Kp0 LC0 1oo0 Wl0 EQN0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 1hB0 1dX0 1ep0 1aL0 1eN0 17X0 1nf0 11z0 1tB0 19W0 1e10 17b0 1ep0 1gL0 18N0 1fz0 1eN0 17b0 1gq0 1gn0 19d0 1dz0 1c10 17X0 1hB0 1gn0 19d0 1dz0 1c10 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0|81e4", "Asia/Kabul|+04 +0430|-40 -4u|01|-10Qs0|46e5", "Asia/Kamchatka|LMT +11 +12 +13|-ay.A -b0 -c0 -d0|012323232323232323232321232323232323232323232323232323232323212|-1SLKy.A ivXy.A 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 2sp0 WM0|18e4", "Asia/Karachi|LMT +0530 +0630 +05 PKT PKST|-4s.c -5u -6u -50 -50 -60|012134545454|-2xoss.c 1qOKW.c 7zX0 eup0 LqMu 1fy00 1cL0 dK10 11b0 1610 1jX0|24e6", "Asia/Urumqi|LMT +06|-5O.k -60|01|-1GgtO.k|32e5", "Asia/Kathmandu|LMT +0530 +0545|-5F.g -5u -5J|012|-21JhF.g 2EGMb.g|12e5", "Asia/Khandyga|LMT +08 +09 +10 +11|-92.d -80 -90 -a0 -b0|0123232323232323232323212323232323232323232323232343434343434343432|-21Q92.d pAp2.d 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 qK0 yN0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0|66e2", "Asia/Krasnoyarsk|LMT +06 +07 +08|-6b.q -60 -70 -80|01232323232323232323232123232323232323232323232323232323232323232|-21Hib.q prAb.q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|10e5", "Asia/Kuala_Lumpur|SMT +07 +0720 +0730 +09 +08|-6T.p -70 -7k -7u -90 -80|0123435|-2Bg6T.p 17anT.p l5XE 17bO 8Fyu 1so1u|71e5", "Asia/Kuching|LMT +0730 +08 +0820 +09|-7l.k -7u -80 -8k -90|0123232323232323242|-1KITl.k gDbP.k 6ynu AnE 1O0k AnE 1NAk AnE 1NAk AnE 1NAk AnE 1O0k AnE 1NAk AnE pAk 8Fz0|13e4", "Asia/Macau|LMT CST CDT|-7y.k -80 -90|012121212121212121212121212121212121212121|-2le7y.k 1XO34.k 1wn0 Rd0 1wn0 R9u 1wqu U10 1tz0 TVu 1tz0 17gu 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cOu 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cL0|57e4", "Asia/Magadan|LMT +10 +11 +12|-a3.c -a0 -b0 -c0|012323232323232323232321232323232323232323232323232323232323232312|-1Pca3.c eUo3.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3Cq0|95e3", "Asia/Makassar|LMT MMT +08 +09 WITA|-7V.A -7V.A -80 -90 -80|01234|-21JjV.A vfc0 myLV.A 8ML0|15e5", "Asia/Manila|+08 +09|-80 -90|010101010|-1kJI0 AL0 cK10 65X0 mXB0 vX0 VK10 1db0|24e6", "Asia/Nicosia|LMT EET EEST|-2d.s -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Vc2d.s 2a3cd.s 1cL0 1qp0 Xz0 19B0 19X0 1fB0 1db0 1cp0 1cL0 1fB0 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1o30 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|32e4", "Asia/Novokuznetsk|LMT +06 +07 +08|-5M.M -60 -70 -80|012323232323232323232321232323232323232323232323232323232323212|-1PctM.M eULM.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 2sp0 WM0|55e4", "Asia/Novosibirsk|LMT +06 +07 +08|-5v.E -60 -70 -80|0123232323232323232323212323212121212121212121212121212121212121212|-21Qnv.E pAFv.E 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 ml0 Os0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 4eN0|15e5", "Asia/Omsk|LMT +05 +06 +07|-4R.u -50 -60 -70|01232323232323232323232123232323232323232323232323232323232323232|-224sR.u pMLR.u 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|12e5", "Asia/Oral|LMT +03 +05 +06 +04|-3p.o -30 -50 -60 -40|01232323232323232424242424242424242424242424242|-1Pc3p.o eUop.o 23CK0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 1cM0 IM0 1EM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|27e4", "Asia/Pontianak|LMT PMT +0730 +09 +08 WITA WIB|-7h.k -7h.k -7u -90 -80 -80 -70|012324256|-2ua7h.k XE00 munL.k 8Rau 6kpu 4PXu xhcu Wqnu|23e4", "Asia/Pyongyang|LMT KST JST KST|-8n -8u -90 -90|01231|-2um8n 97XR 1lTzu 2Onc0|29e5", "Asia/Qyzylorda|LMT +04 +05 +06|-4l.Q -40 -50 -60|0123232323232323232323232323232323232323232323|-1Pc4l.Q eUol.Q 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 3ao0 1EM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|73e4", "Asia/Rangoon|RMT +0630 +09|-6o.E -6u -90|0121|-21Jio.E SmnS.E 7j9u|48e5", "Asia/Sakhalin|LMT +09 +11 +12 +10|-9u.M -90 -b0 -c0 -a0|01232323232323232323232423232323232424242424242424242424242424242|-2AGVu.M 1BoMu.M 1qFa0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 2pB0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0|58e4", "Asia/Samarkand|LMT +04 +05 +06|-4r.R -40 -50 -60|01232323232323232323232|-1Pc4r.R eUor.R 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0|36e4", "Asia/Seoul|LMT KST JST KST KDT KDT|-8r.Q -8u -90 -90 -9u -a0|0123141414141414135353|-2um8r.Q 97XV.Q 1m1zu kKo0 2I0u OL0 1FB0 Rb0 1qN0 TX0 1tB0 TX0 1tB0 TX0 1tB0 TX0 2ap0 12FBu 11A0 1o00 11A0|23e6", "Asia/Srednekolymsk|LMT +10 +11 +12|-ae.Q -a0 -b0 -c0|01232323232323232323232123232323232323232323232323232323232323232|-1Pcae.Q eUoe.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|35e2", "Asia/Taipei|CST JST CDT|-80 -90 -90|01020202020202020202020202020202020202020|-1iw80 joM0 1yo0 Tz0 1ip0 1jX0 1cN0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 10N0 1BX0 10p0 1pz0 10p0 1pz0 10p0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1BB0 ML0 1Bd0 ML0 uq10 1db0 1cN0 1db0 97B0 AL0|74e5", "Asia/Tashkent|LMT +05 +06 +07|-4B.b -50 -60 -70|012323232323232323232321|-1Pc4B.b eUnB.b 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0|23e5", "Asia/Tbilisi|TBMT +03 +04 +05|-2X.b -30 -40 -50|0123232323232323232323212121232323232323232323212|-1Pc2X.b 1jUnX.b WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 1cK0 1cL0 1cN0 1cL0 1cN0 2pz0 1cL0 1fB0 3Nz0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 An0 Os0 WM0|11e5", "Asia/Tehran|LMT TMT +0330 +04 +05 +0430|-3p.I -3p.I -3u -40 -50 -4u|01234325252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2btDp.I 1d3c0 1huLT.I TXu 1pz0 sN0 vAu 1cL0 1dB0 1en0 pNB0 UL0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 64p0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0|14e6", "Asia/Thimphu|LMT +0530 +06|-5W.A -5u -60|012|-Su5W.A 1BGMs.A|79e3", "Asia/Tokyo|JST JDT|-90 -a0|010101010|-QJH0 QL0 1lB0 13X0 1zB0 NX0 1zB0 NX0|38e6", "Asia/Tomsk|LMT +06 +07 +08|-5D.P -60 -70 -80|0123232323232323232323212323232323232323232323212121212121212121212|-21NhD.P pxzD.P 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 co0 1bB0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3Qp0|10e5", "Asia/Ulaanbaatar|LMT +07 +08 +09|-77.w -70 -80 -90|012323232323232323232323232323232323232323232323232|-2APH7.w 2Uko7.w cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0|12e5", "Asia/Ust-Nera|LMT +08 +09 +12 +11 +10|-9w.S -80 -90 -c0 -b0 -a0|012343434343434343434345434343434343434343434343434343434343434345|-21Q9w.S pApw.S 23CL0 1d90 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0|65e2", "Asia/Vladivostok|LMT +09 +10 +11|-8L.v -90 -a0 -b0|01232323232323232323232123232323232323232323232323232323232323232|-1SJIL.v itXL.v 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|60e4", "Asia/Yakutsk|LMT +08 +09 +10|-8C.W -80 -90 -a0|01232323232323232323232123232323232323232323232323232323232323232|-21Q8C.W pAoC.W 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|28e4", "Asia/Yekaterinburg|LMT PMT +04 +05 +06|-42.x -3J.5 -40 -50 -60|012343434343434343434343234343434343434343434343434343434343434343|-2ag42.x 7mQh.s qBvJ.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|14e5", "Asia/Yerevan|LMT +03 +04 +05|-2W -30 -40 -50|0123232323232323232323212121212323232323232323232323232323232|-1Pc2W 1jUnW WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 4RX0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|13e5", "Atlantic/Azores|HMT -02 -01 +00 WET|1S.w 20 10 0 0|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121232323232323232323232323232323234323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2ldW5.s aPX5.s Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|25e4", "Atlantic/Bermuda|LMT AST ADT|4j.i 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1BnRE.G 1LTbE.G 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|65e3", "Atlantic/Canary|LMT -01 WET WEST|11.A 10 0 -10|01232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UtaW.o XPAW.o 1lAK0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4", "Atlantic/Cape_Verde|LMT -02 -01|1y.4 20 10|01212|-2xomp.U 1qOMp.U 7zX0 1djf0|50e4", "Atlantic/Faroe|LMT WET WEST|r.4 0 -10|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2uSnw.U 2Wgow.U 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|49e3", "Atlantic/Madeira|FMT -01 +00 +01 WET WEST|17.A 10 0 -10 0 -10|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2ldWQ.o aPWQ.o Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e4", "Atlantic/Reykjavik|LMT -01 +00 GMT|1s 10 0 0|012121212121212121212121212121212121212121212121212121212121212121213|-2uWmw mfaw 1Bd0 ML0 1LB0 Cn0 1LB0 3fX0 C10 HrX0 1cO0 LB0 1EL0 LA0 1C00 Oo0 1wo0 Rc0 1wo0 Rc0 1wo0 Rc0 1zc0 Oo0 1zc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0|12e4", "Atlantic/South_Georgia|-02|20|0||30", "Atlantic/Stanley|SMT -04 -03 -02|3P.o 40 30 20|012121212121212323212121212121212121212121212121212121212121212121212|-2kJw8.A 12bA8.A 19X0 1fB0 19X0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 Cn0 1Cc10 WL0 1qL0 U10 1tz0 2mN0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 U10 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qN0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 U10 1tz0 U10 1tz0 U10|21e2", "Australia/Sydney|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|40e5", "Australia/Adelaide|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 WM0 1qM0 Rc0 1zc0 U00 1tA0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|11e5", "Australia/Brisbane|AEST AEDT|-a0 -b0|01010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0|20e5", "Australia/Broken_Hill|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|18e3", "Australia/Currie|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|746", "Australia/Darwin|ACST ACDT|-9u -au|010101010|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0|12e4", "Australia/Eucla|+0845 +0945|-8J -9J|0101010101010101010|-293kI xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0|368", "Australia/Hobart|AEST AEDT|-a0 -b0|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 VfB0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|21e4", "Australia/Lord_Howe|AEST +1030 +1130 +11|-a0 -au -bu -b0|0121212121313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313|raC0 1zdu Rb0 1zd0 On0 1zd0 On0 1zd0 On0 1zd0 TXu 1qMu WLu 1tAu WLu 1tAu TXu 1tAu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu 11zu 1o0u 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 11Au 1nXu 1qMu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu 11zu 1o0u WLu 1qMu 14nu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1fzu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu|347", "Australia/Lindeman|AEST AEDT|-a0 -b0|010101010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0|10", "Australia/Melbourne|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1qM0 11A0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|39e5", "Australia/Perth|AWST AWDT|-80 -90|0101010101010101010|-293jX xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0|18e5", "CET|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", "CST6CDT|CST CDT CWT CPT|60 50 50 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "Pacific/Easter|EMT -07 -06 -05|7h.s 70 60 50|012121212121212121212121212123232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-1uSgG.w 1s4IG.w WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 2pA0 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|30e2", "EET|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", "EST|EST|50|0|", "EST5EDT|EST EDT EWT EPT|50 40 40 40|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 SgN0 8x40 iv0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "Europe/Dublin|DMT IST GMT BST IST|p.l -y.D 0 -10 -10|01232323232324242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242|-2ax9y.D Rc0 1fzy.D 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 g5X0 14p0 1wn0 17d0 1io0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", "Etc/GMT+0|GMT|0|0|", "Etc/GMT+1|-01|10|0|", "Etc/GMT+10|-10|a0|0|", "Etc/GMT+11|-11|b0|0|", "Etc/GMT+12|-12|c0|0|", "Etc/GMT+3|-03|30|0|", "Etc/GMT+4|-04|40|0|", "Etc/GMT+5|-05|50|0|", "Etc/GMT+6|-06|60|0|", "Etc/GMT+7|-07|70|0|", "Etc/GMT+8|-08|80|0|", "Etc/GMT+9|-09|90|0|", "Etc/GMT-1|+01|-10|0|", "Pacific/Port_Moresby|+10|-a0|0||25e4", "Pacific/Pohnpei|+11|-b0|0||34e3", "Pacific/Tarawa|+12|-c0|0||29e3", "Etc/GMT-13|+13|-d0|0|", "Etc/GMT-14|+14|-e0|0|", "Etc/GMT-2|+02|-20|0|", "Etc/GMT-3|+03|-30|0|", "Etc/GMT-4|+04|-40|0|", "Etc/GMT-5|+05|-50|0|", "Etc/GMT-6|+06|-60|0|", "Indian/Christmas|+07|-70|0||21e2", "Etc/GMT-8|+08|-80|0|", "Pacific/Palau|+09|-90|0||21e3", "Etc/UCT|UCT|0|0|", "Etc/UTC|UTC|0|0|", "Europe/Amsterdam|AMT NST +0120 +0020 CEST CET|-j.w -1j.w -1k -k -20 -10|010101010101010101010101010101010101010101012323234545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2aFcj.w 11b0 1iP0 11A0 1io0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1co0 1io0 1yo0 Pc0 1a00 1fA0 1Bc0 Mo0 1tc0 Uo0 1tA0 U00 1uo0 W00 1s00 VA0 1so0 Vc0 1sM0 UM0 1wo0 Rc0 1u00 Wo0 1rA0 W00 1s00 VA0 1sM0 UM0 1w00 fV0 BCX.w 1tA0 U00 1u00 Wo0 1sm0 601k WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|16e5", "Europe/Andorra|WET CET CEST|0 -10 -20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-UBA0 1xIN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|79e3", "Europe/Astrakhan|LMT +03 +04 +05|-3c.c -30 -40 -50|012323232323232323212121212121212121212121212121212121212121212|-1Pcrc.c eUMc.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0", "Europe/Athens|AMT EET EEST CEST CET|-1y.Q -20 -30 -20 -10|012123434121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a61x.Q CNbx.Q mn0 kU10 9b0 3Es0 Xa0 1fb0 1dd0 k3X0 Nz0 SCp0 1vc0 SO0 1cM0 1a00 1ao0 1fc0 1a10 1fG0 1cg0 1dX0 1bX0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|35e5", "Europe/London|GMT BST BDST|0 -10 -20|0101010101010101010101010101010101010101010101010121212121210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|10e6", "Europe/Belgrade|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19RC0 3IP0 WM0 1fA0 1cM0 1cM0 1rc0 Qo0 1vmo0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", "Europe/Berlin|CET CEST CEMT|-10 -20 -30|01010101010101210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 kL0 Nc0 m10 WM0 1ao0 1cp0 dX0 jz0 Dd0 1io0 17c0 1fA0 1a00 1ehA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|41e5", "Europe/Prague|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 16M0 1lc0 1tA0 17A0 11c0 1io0 17c0 1io0 17c0 1fc0 1ao0 1bNc0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|13e5", "Europe/Brussels|WET CET CEST WEST|0 -10 -20 -10|0121212103030303030303030303030303030303030303030303212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ehc0 3zX0 11c0 1iO0 11A0 1o00 11A0 my0 Ic0 1qM0 Rc0 1EM0 UM0 1u00 10o0 1io0 1io0 17c0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a30 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 y00 5Wn0 WM0 1fA0 1cM0 16M0 1iM0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|21e5", "Europe/Bucharest|BMT EET EEST|-1I.o -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1xApI.o 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Axc0 On0 1fA0 1a10 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|19e5", "Europe/Budapest|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1ip0 17b0 1op0 1tb0 Q2m0 3Ne0 WM0 1fA0 1cM0 1cM0 1oJ0 1dc0 1030 1fA0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1iM0 1fA0 8Ha0 Rb0 1wN0 Rb0 1BB0 Lz0 1C20 LB0 SNX0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e5", "Europe/Zurich|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19Lc0 11A0 1o00 11A0 1xG10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|38e4", "Europe/Chisinau|CMT BMT EET EEST CEST CET MSK MSD|-1T -1I.o -20 -30 -20 -10 -30 -40|012323232323232323234545467676767676767676767323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-26jdT wGMa.A 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 27A0 2en0 39g0 WM0 1fA0 1cM0 V90 1t7z0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 gL0 WO0 1cM0 1cM0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11D0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|67e4", "Europe/Copenhagen|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 Tz0 VuO0 60q0 WM0 1fA0 1cM0 1cM0 1cM0 S00 1HA0 Nc0 1C00 Dc0 1Nc0 Ao0 1h5A0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", "Europe/Gibraltar|GMT BST BDST CET CEST|0 -10 -20 -10 -20|010101010101010101010101010101010101010101010101012121212121010121010101010101010101034343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 10Jz0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|30e3", "Europe/Helsinki|HMT EET EEST|-1D.N -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1WuND.N OULD.N 1dA0 1xGq0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", "Europe/Kaliningrad|CET CEST CET CEST MSK MSD EEST EET +03|-10 -20 -20 -30 -30 -40 -30 -20 -30|0101010101010232454545454545454546767676767676767676767676767676767676767676787|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 Am0 Lb0 1en0 op0 1pNz0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|44e4", "Europe/Kiev|KMT EET MSK CEST CET MSD EEST|-22.4 -20 -30 -20 -10 -40 -30|0123434252525252525252525256161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc22.4 eUo2.4 rnz0 2Hg0 WM0 1fA0 da0 1v4m0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 Db0 3220 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|34e5", "Europe/Kirov|LMT +03 +04 +05|-3i.M -30 -40 -50|01232323232323232321212121212121212121212121212121212121212121|-22WM0 qH90 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|48e4", "Europe/Lisbon|LMT WET WEST WEMT CET CEST|A.J 0 -10 -20 -10 -20|012121212121212121212121212121212121212121212321232123212321212121212121212121212121212121212121214121212121212121212121212121212124545454212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ldXn.f aPWn.f Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 pvy0 1cM0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e5", "Europe/Luxembourg|LMT CET CEST WET WEST WEST WET|-o.A -10 -20 0 -10 -20 -10|0121212134343434343434343434343434343434343434343434565651212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2DG0o.A t6mo.A TB0 1nX0 Up0 1o20 11A0 rW0 CM0 1qP0 R90 1EO0 UK0 1u20 10m0 1ip0 1in0 17e0 19W0 1fB0 1db0 1cp0 1in0 17d0 1fz0 1a10 1in0 1a10 1in0 17f0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 vA0 60L0 WM0 1fA0 1cM0 17c0 1io0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4", "Europe/Madrid|WET WEST WEMT CET CEST|0 -10 -20 -10 -20|010101010101010101210343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-25Td0 19B0 1cL0 1dd0 b1z0 18p0 3HX0 17d0 1fz0 1a10 1io0 1a00 1in0 17d0 iIn0 Hd0 1cL0 bb0 1200 2s20 14n0 5aL0 Mp0 1vz0 17d0 1in0 17d0 1in0 17d0 1in0 17d0 6hX0 11B0 XHX0 1a10 1fz0 1a10 19X0 1cN0 1fz0 1a10 1fC0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|62e5", "Europe/Malta|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2arB0 Lz0 1cN0 1db0 1410 1on0 Wp0 1qL0 17d0 1cL0 M3B0 5M20 WM0 1fA0 1co0 17c0 1iM0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 LA0 1C00 LA0 1EM0 LA0 1C00 LA0 1zc0 Oo0 1C00 Oo0 1co0 1cM0 1lA0 Xc0 1qq0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1iN0 19z0 1fB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|42e4", "Europe/Minsk|MMT EET MSK CEST CET MSD EEST +03|-1O -20 -30 -20 -10 -40 -30 -30|01234343252525252525252525261616161616161616161616161616161616161617|-1Pc1O eUnO qNX0 3gQ0 WM0 1fA0 1cM0 Al0 1tsn0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 3Fc0 1cN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0|19e5", "Europe/Monaco|PMT WET WEST WEMT CET CEST|-9.l 0 -10 -20 -10 -20|01212121212121212121212121212121212121212121212121232323232345454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 2RV0 11z0 11B0 1ze0 WM0 1fA0 1cM0 1fa0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|38e3", "Europe/Moscow|MMT MMT MST MDST MSD MSK +05 EET EEST MSK|-2u.h -2v.j -3v.j -4v.j -40 -30 -50 -20 -30 -40|012132345464575454545454545454545458754545454545454545454545454545454545454595|-2ag2u.h 2pyW.W 1bA0 11X0 GN0 1Hb0 c4v.j ik0 3DA0 dz0 15A0 c10 2q10 iM10 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|16e6", "Europe/Paris|PMT WET WEST CEST CET WEMT|-9.l 0 -10 -20 -10 -20|0121212121212121212121212121212121212121212121212123434352543434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-2nco8.l cNb8.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 Ik0 5M30 WM0 1fA0 1cM0 Vx0 hB0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e6", "Europe/Riga|RMT LST EET MSK CEST CET MSD EEST|-1A.y -2A.y -20 -30 -20 -10 -40 -30|010102345454536363636363636363727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272|-25TzA.y 11A0 1iM0 ko0 gWm0 yDXA.y 2bX0 3fE0 WM0 1fA0 1cM0 1cM0 4m0 1sLy0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 1o00 11A0 1o00 11A0 1qM0 3oo0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|64e4", "Europe/Rome|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2arB0 Lz0 1cN0 1db0 1410 1on0 Wp0 1qL0 17d0 1cL0 M3B0 5M20 WM0 1fA0 1cM0 16M0 1iM0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 LA0 1C00 LA0 1EM0 LA0 1C00 LA0 1zc0 Oo0 1C00 Oo0 1C00 LA0 1zc0 Oo0 1C00 LA0 1C00 LA0 1zc0 Oo0 1C00 Oo0 1zc0 Oo0 1fC0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|39e5", "Europe/Samara|LMT +03 +04 +05|-3k.k -30 -40 -50|0123232323232323232121232323232323232323232323232323232323212|-22WM0 qH90 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 2y10 14m0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 2sp0 WM0|12e5", "Europe/Saratov|LMT +03 +04 +05|-34.i -30 -40 -50|012323232323232321212121212121212121212121212121212121212121212|-22WM0 qH90 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1cM0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 5810", "Europe/Simferopol|SMT EET MSK CEST CET MSD EEST MSK|-2g -20 -30 -20 -10 -40 -30 -40|012343432525252525252525252161616525252616161616161616161616161616161616172|-1Pc2g eUog rEn0 2qs0 WM0 1fA0 1cM0 3V0 1u0L0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 4eL0 1cL0 1cN0 1cL0 1cN0 dX0 WL0 1cN0 1cL0 1fB0 1o30 11B0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0|33e4", "Europe/Sofia|EET CET CEST EEST|-20 -10 -20 -30|01212103030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030|-168L0 WM0 1fA0 1cM0 1cM0 1cN0 1mKH0 1dd0 1fb0 1ap0 1fb0 1a20 1fy0 1a30 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5", "Europe/Stockholm|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 TB0 2yDe0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|15e5", "Europe/Tallinn|TMT CET CEST EET MSK MSD EEST|-1D -10 -20 -20 -30 -40 -30|012103421212454545454545454546363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363|-26oND teD 11A0 1Ta0 4rXl KSLD 2FX0 2Jg0 WM0 1fA0 1cM0 18J0 1sTX0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o10 11A0 1qM0 5QM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|41e4", "Europe/Tirane|LMT CET CEST|-1j.k -10 -20|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2glBj.k 14pcj.k 5LC0 WM0 4M0 1fCK0 10n0 1op0 11z0 1pd0 11z0 1qN0 WL0 1qp0 Xb0 1qp0 Xb0 1qp0 11z0 1lB0 11z0 1qN0 11z0 1iN0 16n0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|42e4", "Europe/Ulyanovsk|LMT +03 +04 +05 +02|-3d.A -30 -40 -50 -20|01232323232323232321214121212121212121212121212121212121212121212|-22WM0 qH90 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0", "Europe/Uzhgorod|CET CEST MSK MSD EET EEST|-10 -20 -30 -40 -20 -30|010101023232323232323232320454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-1cqL0 6i00 WM0 1fA0 1cM0 1ml0 1Cp0 1r3W0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 1Nf0 2pw0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e4", "Europe/Vienna|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 3KM0 14o0 LA00 6i00 WM0 1fA0 1cM0 1cM0 1cM0 400 2qM0 1a00 1cM0 1cM0 1io0 17c0 1gHa0 19X0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|18e5", "Europe/Vilnius|WMT KMT CET EET MSK CEST MSD EEST|-1o -1z.A -10 -20 -30 -20 -40 -30|012324525254646464646464646473737373737373737352537373737373737373737373737373737373737373737373737373737373737373737373|-293do 6ILM.o 1Ooz.A zz0 Mfd0 29W0 3is0 WM0 1fA0 1cM0 LV0 1tgL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11B0 1o00 11A0 1qM0 8io0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4", "Europe/Volgograd|LMT +03 +04 +05|-2V.E -30 -40 -50|01232323232323232121212121212121212121212121212121212121212121|-21IqV.E psLV.E 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1cM0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|10e5", "Europe/Warsaw|WMT CET CEST EET EEST|-1o -10 -20 -20 -30|012121234312121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ctdo 1LXo 11d0 1iO0 11A0 1o00 11A0 1on0 11A0 6zy0 HWP0 5IM0 WM0 1fA0 1cM0 1dz0 1mL0 1en0 15B0 1aq0 1nA0 11A0 1io0 17c0 1fA0 1a00 iDX0 LA0 1cM0 1cM0 1C00 Oo0 1cM0 1cM0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1C00 LA0 uso0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e5", "Europe/Zaporozhye|+0220 EET MSK CEST CET MSD EEST|-2k -20 -30 -20 -10 -40 -30|01234342525252525252525252526161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc2k eUok rdb0 2RE0 WM0 1fA0 8m0 1v9a0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cK0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|77e4", "HST|HST|a0|0|", "Indian/Chagos|LMT +05 +06|-4N.E -50 -60|012|-2xosN.E 3AGLN.E|30e2", "Indian/Cocos|+0630|-6u|0||596", "Indian/Kerguelen|-00 +05|0 -50|01|-MG00|130", "Indian/Mahe|LMT +04|-3F.M -40|01|-2yO3F.M|79e3", "Indian/Maldives|MMT +05|-4S -50|01|-olgS|35e4", "Indian/Mauritius|LMT +04 +05|-3O -40 -50|012121|-2xorO 34unO 14L0 12kr0 11z0|15e4", "Indian/Reunion|LMT +04|-3F.Q -40|01|-2mDDF.Q|84e4", "Pacific/Kwajalein|+11 -12 +12|-b0 c0 -c0|012|-AX0 W9X0|14e3", "MET|MET MEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", "MST|MST|70|0|", "MST7MDT|MST MDT MWT MPT|70 60 60 60|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "Pacific/Chatham|+1215 +1245 +1345|-cf -cJ -dJ|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-WqAf 1adef IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|600", "PST8PDT|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", "Pacific/Apia|LMT -1130 -11 -10 +14 +13|bq.U bu b0 a0 -e0 -d0|01232345454545454545454545454545454545454545454545454545454|-2nDMx.4 1yW03.4 2rRbu 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|37e3", "Pacific/Bougainville|+10 +09 +11|-a0 -90 -b0|0102|-16Wy0 7CN0 2MQp0|18e4", "Pacific/Efate|LMT +11 +12|-bd.g -b0 -c0|0121212121212121212121|-2l9nd.g 2Szcd.g 1cL0 1oN0 10L0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 Lz0 1Nd0 An0|66e3", "Pacific/Enderbury|-12 -11 +13|c0 b0 -d0|012|nIc0 B8n0|1", "Pacific/Fakaofo|-11 +13|b0 -d0|01|1Gfn0|483", "Pacific/Fiji|LMT +12 +13|-bT.I -c0 -d0|0121212121212121212121212121212121212121212121212121212121212121|-2bUzT.I 3m8NT.I LA0 1EM0 IM0 nJc0 LA0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0|88e4", "Pacific/Galapagos|LMT -05 -06|5W.o 50 60|01212|-1yVS1.A 2dTz1.A gNd0 rz0|25e3", "Pacific/Gambier|LMT -09|8X.M 90|01|-2jof0.c|125", "Pacific/Guadalcanal|LMT +11|-aD.M -b0|01|-2joyD.M|11e4", "Pacific/Guam|GST ChST|-a0 -a0|01|1fpq0|17e4", "Pacific/Honolulu|HST HDT HST|au 9u a0|010102|-1thLu 8x0 lef0 8Pz0 46p0|37e4", "Pacific/Kiritimati|-1040 -10 +14|aE a0 -e0|012|nIaE B8nk|51e2", "Pacific/Kosrae|+11 +12|-b0 -c0|010|-AX0 1bdz0|66e2", "Pacific/Majuro|+11 +12|-b0 -c0|01|-AX0|28e3", "Pacific/Marquesas|LMT -0930|9i 9u|01|-2joeG|86e2", "Pacific/Pago_Pago|LMT SST|bm.M b0|01|-2nDMB.c|37e2", "Pacific/Nauru|LMT +1130 +09 +12|-b7.E -bu -90 -c0|01213|-1Xdn7.E PvzB.E 5RCu 1ouJu|10e3", "Pacific/Niue|-1120 -1130 -11|bk bu b0|012|-KfME 17y0a|12e2", "Pacific/Norfolk|+1112 +1130 +1230 +11|-bc -bu -cu -b0|01213|-Kgbc W01G On0 1COp0|25e4", "Pacific/Noumea|LMT +11 +12|-b5.M -b0 -c0|01212121|-2l9n5.M 2EqM5.M xX0 1PB0 yn0 HeP0 Ao0|98e3", "Pacific/Pitcairn|-0830 -08|8u 80|01|18Vku|56", "Pacific/Rarotonga|-1030 -0930 -10|au 9u a0|012121212121212121212121212|lyWu IL0 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu|13e3", "Pacific/Tahiti|LMT -10|9W.g a0|01|-2joe1.I|18e4", "Pacific/Tongatapu|+1220 +13 +14|-ck -d0 -e0|0121212121212121212121212121212121212121212121212121|-1aB0k 2n5dk 15A0 1wo0 xz0 1Q10 xz0 zWN0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0|75e3", "WET|WET WEST|0 -10|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00" ], "links": [ "Africa/Abidjan|Africa/Bamako", "Africa/Abidjan|Africa/Banjul", "Africa/Abidjan|Africa/Conakry", "Africa/Abidjan|Africa/Dakar", "Africa/Abidjan|Africa/Freetown", "Africa/Abidjan|Africa/Lome", "Africa/Abidjan|Africa/Nouakchott", "Africa/Abidjan|Africa/Ouagadougou", "Africa/Abidjan|Africa/Sao_Tome", "Africa/Abidjan|Africa/Timbuktu", "Africa/Abidjan|Atlantic/St_Helena", "Africa/Cairo|Egypt", "Africa/Johannesburg|Africa/Maseru", "Africa/Johannesburg|Africa/Mbabane", "Africa/Khartoum|Africa/Juba", "Africa/Lagos|Africa/Bangui", "Africa/Lagos|Africa/Brazzaville", "Africa/Lagos|Africa/Douala", "Africa/Lagos|Africa/Kinshasa", "Africa/Lagos|Africa/Libreville", "Africa/Lagos|Africa/Luanda", "Africa/Lagos|Africa/Malabo", "Africa/Lagos|Africa/Niamey", "Africa/Lagos|Africa/Porto-Novo", "Africa/Maputo|Africa/Blantyre", "Africa/Maputo|Africa/Bujumbura", "Africa/Maputo|Africa/Gaborone", "Africa/Maputo|Africa/Harare", "Africa/Maputo|Africa/Kigali", "Africa/Maputo|Africa/Lubumbashi", "Africa/Maputo|Africa/Lusaka", "Africa/Nairobi|Africa/Addis_Ababa", "Africa/Nairobi|Africa/Asmara", "Africa/Nairobi|Africa/Asmera", "Africa/Nairobi|Africa/Dar_es_Salaam", "Africa/Nairobi|Africa/Djibouti", "Africa/Nairobi|Africa/Kampala", "Africa/Nairobi|Africa/Mogadishu", "Africa/Nairobi|Indian/Antananarivo", "Africa/Nairobi|Indian/Comoro", "Africa/Nairobi|Indian/Mayotte", "Africa/Tripoli|Libya", "America/Adak|America/Atka", "America/Adak|US/Aleutian", "America/Anchorage|US/Alaska", "America/Argentina/Buenos_Aires|America/Buenos_Aires", "America/Argentina/Catamarca|America/Argentina/ComodRivadavia", "America/Argentina/Catamarca|America/Catamarca", "America/Argentina/Cordoba|America/Cordoba", "America/Argentina/Cordoba|America/Rosario", "America/Argentina/Jujuy|America/Jujuy", "America/Argentina/Mendoza|America/Mendoza", "America/Atikokan|America/Coral_Harbour", "America/Chicago|US/Central", "America/Curacao|America/Aruba", "America/Curacao|America/Kralendijk", "America/Curacao|America/Lower_Princes", "America/Denver|America/Shiprock", "America/Denver|Navajo", "America/Denver|US/Mountain", "America/Detroit|US/Michigan", "America/Edmonton|Canada/Mountain", "America/Fort_Wayne|America/Indiana/Indianapolis", "America/Fort_Wayne|America/Indianapolis", "America/Fort_Wayne|US/East-Indiana", "America/Halifax|Canada/Atlantic", "America/Havana|Cuba", "America/Indiana/Knox|America/Knox_IN", "America/Indiana/Knox|US/Indiana-Starke", "America/Jamaica|Jamaica", "America/Kentucky/Louisville|America/Louisville", "America/Los_Angeles|US/Pacific", "America/Los_Angeles|US/Pacific-New", "America/Manaus|Brazil/West", "America/Mazatlan|Mexico/BajaSur", "America/Mexico_City|Mexico/General", "America/New_York|US/Eastern", "America/Noronha|Brazil/DeNoronha", "America/Panama|America/Cayman", "America/Phoenix|US/Arizona", "America/Port_of_Spain|America/Anguilla", "America/Port_of_Spain|America/Antigua", "America/Port_of_Spain|America/Dominica", "America/Port_of_Spain|America/Grenada", "America/Port_of_Spain|America/Guadeloupe", "America/Port_of_Spain|America/Marigot", "America/Port_of_Spain|America/Montserrat", "America/Port_of_Spain|America/St_Barthelemy", "America/Port_of_Spain|America/St_Kitts", "America/Port_of_Spain|America/St_Lucia", "America/Port_of_Spain|America/St_Thomas", "America/Port_of_Spain|America/St_Vincent", "America/Port_of_Spain|America/Tortola", "America/Port_of_Spain|America/Virgin", "America/Regina|Canada/East-Saskatchewan", "America/Regina|Canada/Saskatchewan", "America/Rio_Branco|America/Porto_Acre", "America/Rio_Branco|Brazil/Acre", "America/Santiago|Chile/Continental", "America/Sao_Paulo|Brazil/East", "America/St_Johns|Canada/Newfoundland", "America/Tijuana|America/Ensenada", "America/Tijuana|America/Santa_Isabel", "America/Tijuana|Mexico/BajaNorte", "America/Toronto|America/Montreal", "America/Toronto|Canada/Eastern", "America/Vancouver|Canada/Pacific", "America/Whitehorse|Canada/Yukon", "America/Winnipeg|Canada/Central", "Asia/Ashgabat|Asia/Ashkhabad", "Asia/Bangkok|Asia/Phnom_Penh", "Asia/Bangkok|Asia/Vientiane", "Asia/Dhaka|Asia/Dacca", "Asia/Dubai|Asia/Muscat", "Asia/Ho_Chi_Minh|Asia/Saigon", "Asia/Hong_Kong|Hongkong", "Asia/Jerusalem|Asia/Tel_Aviv", "Asia/Jerusalem|Israel", "Asia/Kathmandu|Asia/Katmandu", "Asia/Kolkata|Asia/Calcutta", "Asia/Kuala_Lumpur|Asia/Singapore", "Asia/Kuala_Lumpur|Singapore", "Asia/Macau|Asia/Macao", "Asia/Makassar|Asia/Ujung_Pandang", "Asia/Nicosia|Europe/Nicosia", "Asia/Qatar|Asia/Bahrain", "Asia/Rangoon|Asia/Yangon", "Asia/Riyadh|Asia/Aden", "Asia/Riyadh|Asia/Kuwait", "Asia/Seoul|ROK", "Asia/Shanghai|Asia/Chongqing", "Asia/Shanghai|Asia/Chungking", "Asia/Shanghai|Asia/Harbin", "Asia/Shanghai|PRC", "Asia/Taipei|ROC", "Asia/Tehran|Iran", "Asia/Thimphu|Asia/Thimbu", "Asia/Tokyo|Japan", "Asia/Ulaanbaatar|Asia/Ulan_Bator", "Asia/Urumqi|Asia/Kashgar", "Atlantic/Faroe|Atlantic/Faeroe", "Atlantic/Reykjavik|Iceland", "Atlantic/South_Georgia|Etc/GMT+2", "Australia/Adelaide|Australia/South", "Australia/Brisbane|Australia/Queensland", "Australia/Broken_Hill|Australia/Yancowinna", "Australia/Darwin|Australia/North", "Australia/Hobart|Australia/Tasmania", "Australia/Lord_Howe|Australia/LHI", "Australia/Melbourne|Australia/Victoria", "Australia/Perth|Australia/West", "Australia/Sydney|Australia/ACT", "Australia/Sydney|Australia/Canberra", "Australia/Sydney|Australia/NSW", "Etc/GMT+0|Etc/GMT", "Etc/GMT+0|Etc/GMT-0", "Etc/GMT+0|Etc/GMT0", "Etc/GMT+0|Etc/Greenwich", "Etc/GMT+0|GMT", "Etc/GMT+0|GMT+0", "Etc/GMT+0|GMT-0", "Etc/GMT+0|GMT0", "Etc/GMT+0|Greenwich", "Etc/UCT|UCT", "Etc/UTC|Etc/Universal", "Etc/UTC|Etc/Zulu", "Etc/UTC|UTC", "Etc/UTC|Universal", "Etc/UTC|Zulu", "Europe/Belgrade|Europe/Ljubljana", "Europe/Belgrade|Europe/Podgorica", "Europe/Belgrade|Europe/Sarajevo", "Europe/Belgrade|Europe/Skopje", "Europe/Belgrade|Europe/Zagreb", "Europe/Chisinau|Europe/Tiraspol", "Europe/Dublin|Eire", "Europe/Helsinki|Europe/Mariehamn", "Europe/Istanbul|Asia/Istanbul", "Europe/Istanbul|Turkey", "Europe/Lisbon|Portugal", "Europe/London|Europe/Belfast", "Europe/London|Europe/Guernsey", "Europe/London|Europe/Isle_of_Man", "Europe/London|Europe/Jersey", "Europe/London|GB", "Europe/London|GB-Eire", "Europe/Moscow|W-SU", "Europe/Oslo|Arctic/Longyearbyen", "Europe/Oslo|Atlantic/Jan_Mayen", "Europe/Prague|Europe/Bratislava", "Europe/Rome|Europe/San_Marino", "Europe/Rome|Europe/Vatican", "Europe/Warsaw|Poland", "Europe/Zurich|Europe/Busingen", "Europe/Zurich|Europe/Vaduz", "Indian/Christmas|Etc/GMT-7", "Pacific/Auckland|Antarctica/McMurdo", "Pacific/Auckland|Antarctica/South_Pole", "Pacific/Auckland|NZ", "Pacific/Chatham|NZ-CHAT", "Pacific/Easter|Chile/EasterIsland", "Pacific/Guam|Pacific/Saipan", "Pacific/Honolulu|Pacific/Johnston", "Pacific/Honolulu|US/Hawaii", "Pacific/Kwajalein|Kwajalein", "Pacific/Pago_Pago|Pacific/Midway", "Pacific/Pago_Pago|Pacific/Samoa", "Pacific/Pago_Pago|US/Samoa", "Pacific/Palau|Etc/GMT-9", "Pacific/Pohnpei|Etc/GMT-11", "Pacific/Pohnpei|Pacific/Ponape", "Pacific/Port_Moresby|Etc/GMT-10", "Pacific/Port_Moresby|Pacific/Chuuk", "Pacific/Port_Moresby|Pacific/Truk", "Pacific/Port_Moresby|Pacific/Yap", "Pacific/Tarawa|Etc/GMT-12", "Pacific/Tarawa|Pacific/Funafuti", "Pacific/Tarawa|Pacific/Wake", "Pacific/Tarawa|Pacific/Wallis" ] }); return moment; })); prometheus-2.1.0+ds/web/ui/templates/000077500000000000000000000000001323116307200174755ustar00rootroot00000000000000prometheus-2.1.0+ds/web/ui/templates/_base.html000066400000000000000000000056131323116307200214410ustar00rootroot00000000000000 Prometheus Time Series Collection and Processing Server {{template "head" .}} {{template "content" .}} prometheus-2.1.0+ds/web/ui/templates/alerts.html000066400000000000000000000034541323116307200216630ustar00rootroot00000000000000{{define "head"}} {{end}} {{define "content"}}

    Alerts

    {{$alertStateToRowClass := .AlertStateToRowClass}} {{range .AlertingRules}} {{$activeAlerts := .ActiveAlerts}} {{else}} {{end}}
    {{.Name}} ({{len $activeAlerts}} active)
    {{.HTMLSnippet pathPrefix}}
    {{if $activeAlerts}} {{range $activeAlerts}} {{end}}
    Labels State Active Since Value
    {{range $label, $value := .Labels.Map}} {{$label}}="{{$value}}" {{end}} {{.State}} {{.ActiveAt.UTC}} {{.Value}}
    {{end}}
    No alerting rules defined
    {{end}} prometheus-2.1.0+ds/web/ui/templates/config.html000066400000000000000000000002571323116307200216340ustar00rootroot00000000000000{{define "head"}}{{end}} {{define "content"}}

    Configuration

    {{.}}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/flags.html000066400000000000000000000006611323116307200214620ustar00rootroot00000000000000{{define "head"}}{{end}} {{define "content"}}

    Command-Line Flags

    {{range $key, $value := . }} {{end}}
    {{$key}} {{$value}}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/graph.html000066400000000000000000000043701323116307200214700ustar00rootroot00000000000000{{define "head"}} {{end}} {{define "content"}}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/rules.html000066400000000000000000000015461323116307200215230ustar00rootroot00000000000000{{define "head"}} {{end}} {{define "content"}}
    {{range .RuleGroups}} {{range .Rules}} {{end}} {{end}}

    {{.Name}}

    {{humanizeDuration .GetEvaluationTime.Seconds}}

    Rule Evaluation Time
    {{.HTMLSnippet pathPrefix}} {{humanizeDuration .GetEvaluationTime.Seconds}}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/service-discovery.html000066400000000000000000000046701323116307200240370ustar00rootroot00000000000000{{define "head"}} {{end}} {{define "content"}}

    Service Discovery

      {{range $i, $job := .Index}}
    • {{$job}}
    • {{end}}
    {{$targets := .Targets}} {{range $job := .Index}}

    {{$job}}

    {{range index $targets $job}} {{end}}
    {{ end }}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/status.html000066400000000000000000000033341323116307200217110ustar00rootroot00000000000000{{define "head"}}{{end}} {{define "content"}}

    Runtime Information

    Uptime {{.Birth.UTC}}
    Working Directory {{.CWD}}

    Build Information

    Version {{.Version.Version}}
    Revision {{.Version.Revision}}
    Branch {{.Version.Branch}}
    BuildUser {{.Version.BuildUser}}
    BuildDate {{.Version.BuildDate}}
    GoVersion {{.Version.GoVersion}}

    Alertmanagers

    {{range .Alertmanagers}} {{/* Alertmanager URLs always have Scheme, Host and Path set */}} {{end}}
    Endpoint
    {{.Scheme}}://{{.Host}}{{.Path}}
    {{end}} prometheus-2.1.0+ds/web/ui/templates/targets.html000066400000000000000000000055421323116307200220420ustar00rootroot00000000000000{{define "head"}} {{end}} {{define "content"}}

    Targets

    {{range $job, $pool := .TargetPools}} {{$healthy := numHealthy $pool}} {{$total := len $pool}}

    {{$job}} ({{$healthy}}/{{$total}} up)

    {{range $pool}} {{end}}
    Endpoint State Labels Last Scrape Error
    {{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}
    {{range $label, $values := .URL.Query }} {{range $i, $value := $values}} {{$label}}="{{$value}}" {{end}} {{end}}
    {{.Health}} {{$labels := stripLabels .Labels.Map "job"}} {{range $label, $value := $labels}} {{$label}}="{{$value}}" {{else}} none {{end}} {{if .LastScrape.IsZero}}Never{{else}}{{since .LastScrape}} ago{{end}} {{if .LastError}} {{.LastError}} {{end}}
    {{ end }}
    {{end}} prometheus-2.1.0+ds/web/web.go000066400000000000000000000536651323116307200162050ustar00rootroot00000000000000// Copyright 2013 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package web import ( "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" stdlog "log" "net" "net/http" "net/http/pprof" "net/url" "os" "path" "path/filepath" "sort" "strings" "sync" "sync/atomic" "time" "google.golang.org/grpc" pprof_runtime "runtime/pprof" template_text "text/template" "github.com/cockroachdb/cmux" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/mwitkow/go-conntrack" "github.com/opentracing-contrib/go-stdlib/nethttp" "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/common/route" "github.com/prometheus/tsdb" "golang.org/x/net/netutil" "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/retrieval" "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/template" "github.com/prometheus/prometheus/util/httputil" api_v1 "github.com/prometheus/prometheus/web/api/v1" api_v2 "github.com/prometheus/prometheus/web/api/v2" "github.com/prometheus/prometheus/web/ui" ) var localhostRepresentations = []string{"127.0.0.1", "localhost"} // Handler serves various HTTP endpoints of the Prometheus server type Handler struct { logger log.Logger scrapeManager *retrieval.ScrapeManager ruleManager *rules.Manager queryEngine *promql.Engine context context.Context tsdb func() *tsdb.DB storage storage.Storage notifier *notifier.Notifier apiV1 *api_v1.API router *route.Router quitCh chan struct{} reloadCh chan chan error options *Options config *config.Config configString string versionInfo *PrometheusVersion birth time.Time cwd string flagsMap map[string]string externalLabels model.LabelSet mtx sync.RWMutex now func() model.Time ready uint32 // ready is uint32 rather than boolean to be able to use atomic functions. } // ApplyConfig updates the config field of the Handler struct func (h *Handler) ApplyConfig(conf *config.Config) error { h.mtx.Lock() defer h.mtx.Unlock() h.config = conf return nil } // PrometheusVersion contains build information about Prometheus. type PrometheusVersion struct { Version string `json:"version"` Revision string `json:"revision"` Branch string `json:"branch"` BuildUser string `json:"buildUser"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` } // Options for the web Handler. type Options struct { Context context.Context TSDB func() *tsdb.DB Storage storage.Storage QueryEngine *promql.Engine ScrapeManager *retrieval.ScrapeManager RuleManager *rules.Manager Notifier *notifier.Notifier Version *PrometheusVersion Flags map[string]string ListenAddress string ReadTimeout time.Duration MaxConnections int ExternalURL *url.URL RoutePrefix string MetricsPath string UseLocalAssets bool UserAssetsPath string ConsoleTemplatesPath string ConsoleLibrariesPath string EnableLifecycle bool EnableAdminAPI bool } // New initializes a new web Handler. func New(logger log.Logger, o *Options) *Handler { router := route.New() cwd, err := os.Getwd() if err != nil { cwd = "" } if logger == nil { logger = log.NewNopLogger() } h := &Handler{ logger: logger, router: router, quitCh: make(chan struct{}), reloadCh: make(chan chan error), options: o, versionInfo: o.Version, birth: time.Now(), cwd: cwd, flagsMap: o.Flags, context: o.Context, scrapeManager: o.ScrapeManager, ruleManager: o.RuleManager, queryEngine: o.QueryEngine, tsdb: o.TSDB, storage: o.Storage, notifier: o.Notifier, now: model.Now, ready: 0, } h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, h.scrapeManager, h.notifier, func() config.Config { h.mtx.RLock() defer h.mtx.RUnlock() return *h.config }, h.testReady, h.options.TSDB, h.options.EnableAdminAPI, ) if o.RoutePrefix != "/" { // If the prefix is missing for the root path, prepend it. router.Get("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, o.RoutePrefix, http.StatusFound) }) router = router.WithPrefix(o.RoutePrefix) } instrh := prometheus.InstrumentHandler instrf := prometheus.InstrumentHandlerFunc readyf := h.testReady router.Get("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound) }) router.Get("/alerts", readyf(instrf("alerts", h.alerts))) router.Get("/graph", readyf(instrf("graph", h.graph))) router.Get("/status", readyf(instrf("status", h.status))) router.Get("/flags", readyf(instrf("flags", h.flags))) router.Get("/config", readyf(instrf("config", h.serveConfig))) router.Get("/rules", readyf(instrf("rules", h.rules))) router.Get("/targets", readyf(instrf("targets", h.targets))) router.Get("/version", readyf(instrf("version", h.version))) router.Get("/service-discovery", readyf(instrf("servicediscovery", h.serviceDiscovery))) router.Get("/heap", instrf("heap", h.dumpHeap)) router.Get("/metrics", prometheus.Handler().ServeHTTP) router.Get("/federate", readyf(instrh("federate", httputil.CompressionHandler{ Handler: http.HandlerFunc(h.federation), }))) router.Get("/consoles/*filepath", readyf(instrf("consoles", h.consoles))) router.Get("/static/*filepath", instrf("static", h.serveStaticAsset)) if o.UserAssetsPath != "" { router.Get("/user/*filepath", instrf("user", route.FileServe(o.UserAssetsPath))) } if o.EnableLifecycle { router.Post("/-/quit", h.quit) router.Post("/-/reload", h.reload) } else { router.Post("/-/quit", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) w.Write([]byte("Lifecycle APIs are not enabled")) }) router.Post("/-/reload", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) w.Write([]byte("Lifecycle APIs are not enabled")) }) } router.Get("/-/quit", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte("Only POST requests allowed")) }) router.Get("/-/reload", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte("Only POST requests allowed")) }) router.Get("/debug/*subpath", serveDebug) router.Post("/debug/*subpath", serveDebug) router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Prometheus is Healthy.\n") }) router.Get("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Prometheus is Ready.\n") })) return h } var corsHeaders = map[string]string{ "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Date", } // Enables cross-site script calls. func setCORS(w http.ResponseWriter) { for h, v := range corsHeaders { w.Header().Set(h, v) } } func serveDebug(w http.ResponseWriter, req *http.Request) { ctx := req.Context() subpath := route.Param(ctx, "subpath") if subpath == "/pprof" { http.Redirect(w, req, req.URL.Path+"/", http.StatusMovedPermanently) return } if !strings.HasPrefix(subpath, "/pprof/") { http.NotFound(w, req) return } subpath = strings.TrimPrefix(subpath, "/pprof/") switch subpath { case "cmdline": pprof.Cmdline(w, req) case "profile": pprof.Profile(w, req) case "symbol": pprof.Symbol(w, req) case "trace": pprof.Trace(w, req) default: req.URL.Path = "/debug/pprof/" + subpath pprof.Index(w, req) } } func (h *Handler) serveStaticAsset(w http.ResponseWriter, req *http.Request) { fp := route.Param(req.Context(), "filepath") fp = filepath.Join("web/ui/static", fp) info, err := ui.AssetInfo(fp) if err != nil { level.Warn(h.logger).Log("msg", "Could not get file info", "err", err, "file", fp) w.WriteHeader(http.StatusNotFound) return } file, err := ui.Asset(fp) if err != nil { if err != io.EOF { level.Warn(h.logger).Log("msg", "Could not get file", "err", err, "file", fp) } w.WriteHeader(http.StatusNotFound) return } http.ServeContent(w, req, info.Name(), info.ModTime(), bytes.NewReader(file)) } // Ready sets Handler to be ready. func (h *Handler) Ready() { atomic.StoreUint32(&h.ready, 1) } // Verifies whether the server is ready or not. func (h *Handler) isReady() bool { ready := atomic.LoadUint32(&h.ready) if ready == 0 { return false } return true } // Checks if server is ready, calls f if it is, returns 503 if it is not. func (h *Handler) testReady(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if h.isReady() { f(w, r) } else { w.WriteHeader(http.StatusServiceUnavailable) fmt.Fprintf(w, "Service Unavailable") } } } // Checks if server is ready, calls f if it is, returns 503 if it is not. func (h *Handler) testReadyHandler(f http.Handler) http.HandlerFunc { return h.testReady(f.ServeHTTP) } // Quit returns the receive-only quit channel. func (h *Handler) Quit() <-chan struct{} { return h.quitCh } // Reload returns the receive-only channel that signals configuration reload requests. func (h *Handler) Reload() <-chan chan error { return h.reloadCh } // Run serves the HTTP endpoints. func (h *Handler) Run(ctx context.Context) error { level.Info(h.logger).Log("msg", "Start listening for connections", "address", h.options.ListenAddress) listener, err := net.Listen("tcp", h.options.ListenAddress) if err != nil { return err } listener = netutil.LimitListener(listener, h.options.MaxConnections) // Monitor incoming connections with conntrack. listener = conntrack.NewListener(listener, conntrack.TrackWithName("http"), conntrack.TrackWithTracing()) var ( m = cmux.New(listener) grpcl = m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc")) httpl = m.Match(cmux.HTTP1Fast()) grpcSrv = grpc.NewServer() ) av2 := api_v2.New( time.Now, h.options.TSDB, h.options.QueryEngine, h.options.Storage.Querier, func() []*retrieval.Target { return h.options.ScrapeManager.Targets() }, func() []*url.URL { return h.options.Notifier.Alertmanagers() }, h.options.EnableAdminAPI, ) av2.RegisterGRPC(grpcSrv) hh, err := av2.HTTPHandler(h.options.ListenAddress) if err != nil { return err } hhFunc := h.testReadyHandler(hh) operationName := nethttp.OperationNameFunc(func(r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, r.URL.Path) }) mux := http.NewServeMux() mux.Handle("/", h.router) av1 := route.New() h.apiV1.Register(av1) apiPath := "/api" if h.options.RoutePrefix != "/" { apiPath = h.options.RoutePrefix + apiPath level.Info(h.logger).Log("msg", "router prefix", "prefix", h.options.RoutePrefix) } mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1)) mux.Handle(apiPath+"/", http.StripPrefix(apiPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { setCORS(w) hhFunc(w, r) }), )) errlog := stdlog.New(log.NewStdlibAdapter(level.Error(h.logger)), "", 0) httpSrv := &http.Server{ Handler: nethttp.Middleware(opentracing.GlobalTracer(), mux, operationName), ErrorLog: errlog, ReadTimeout: h.options.ReadTimeout, } go func() { if err := httpSrv.Serve(httpl); err != nil { level.Warn(h.logger).Log("msg", "error serving HTTP", "err", err) } }() go func() { if err := grpcSrv.Serve(grpcl); err != nil { level.Warn(h.logger).Log("msg", "error serving gRPC", "err", err) } }() errCh := make(chan error) go func() { errCh <- m.Serve() }() select { case e := <-errCh: return e case <-ctx.Done(): httpSrv.Shutdown(ctx) grpcSrv.GracefulStop() return nil } } func (h *Handler) alerts(w http.ResponseWriter, r *http.Request) { alerts := h.ruleManager.AlertingRules() alertsSorter := byAlertStateAndNameSorter{alerts: alerts} sort.Sort(alertsSorter) alertStatus := AlertStatus{ AlertingRules: alertsSorter.alerts, AlertStateToRowClass: map[rules.AlertState]string{ rules.StateInactive: "success", rules.StatePending: "warning", rules.StateFiring: "danger", }, } h.executeTemplate(w, "alerts.html", alertStatus) } func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { ctx := r.Context() name := route.Param(ctx, "filepath") file, err := http.Dir(h.options.ConsoleTemplatesPath).Open(name) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } text, err := ioutil.ReadAll(file) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Provide URL parameters as a map for easy use. Advanced users may have need for // parameters beyond the first, so provide RawParams. rawParams, err := url.ParseQuery(r.URL.RawQuery) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } params := map[string]string{} for k, v := range rawParams { params[k] = v[0] } data := struct { RawParams url.Values Params map[string]string Path string }{ RawParams: rawParams, Params: params, Path: strings.TrimLeft(name, "/"), } tmpl := template.NewTemplateExpander( h.context, string(text), "__console_"+name, data, h.now(), template.QueryFunc(rules.EngineQueryFunc(h.queryEngine)), h.options.ExternalURL, ) filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } result, err := tmpl.ExpandHTML(filenames) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } io.WriteString(w, result) } func (h *Handler) graph(w http.ResponseWriter, r *http.Request) { h.executeTemplate(w, "graph.html", nil) } func (h *Handler) status(w http.ResponseWriter, r *http.Request) { h.executeTemplate(w, "status.html", struct { Birth time.Time CWD string Version *PrometheusVersion Alertmanagers []*url.URL }{ Birth: h.birth, CWD: h.cwd, Version: h.versionInfo, Alertmanagers: h.notifier.Alertmanagers(), }) } func (h *Handler) flags(w http.ResponseWriter, r *http.Request) { h.executeTemplate(w, "flags.html", h.flagsMap) } func (h *Handler) serveConfig(w http.ResponseWriter, r *http.Request) { h.mtx.RLock() defer h.mtx.RUnlock() h.executeTemplate(w, "config.html", h.config.String()) } func (h *Handler) rules(w http.ResponseWriter, r *http.Request) { h.executeTemplate(w, "rules.html", h.ruleManager) } func (h *Handler) serviceDiscovery(w http.ResponseWriter, r *http.Request) { var index []string targets := h.scrapeManager.TargetMap() for job := range targets { index = append(index, job) } sort.Strings(index) scrapeConfigData := struct { Index []string Targets map[string][]*retrieval.Target }{ Index: index, Targets: targets, } h.executeTemplate(w, "service-discovery.html", scrapeConfigData) } func (h *Handler) targets(w http.ResponseWriter, r *http.Request) { // Bucket targets by job label tps := map[string][]*retrieval.Target{} for _, t := range h.scrapeManager.Targets() { job := t.Labels().Get(model.JobLabel) tps[job] = append(tps[job], t) } for _, targets := range tps { sort.Slice(targets, func(i, j int) bool { return targets[i].Labels().Get(labels.InstanceName) < targets[j].Labels().Get(labels.InstanceName) }) } h.executeTemplate(w, "targets.html", struct { TargetPools map[string][]*retrieval.Target }{ TargetPools: tps, }) } func (h *Handler) version(w http.ResponseWriter, r *http.Request) { dec := json.NewEncoder(w) if err := dec.Encode(h.versionInfo); err != nil { http.Error(w, fmt.Sprintf("error encoding JSON: %s", err), http.StatusInternalServerError) } } func (h *Handler) quit(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Requesting termination... Goodbye!") close(h.quitCh) } func (h *Handler) reload(w http.ResponseWriter, r *http.Request) { rc := make(chan error) h.reloadCh <- rc if err := <-rc; err != nil { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } } func (h *Handler) consolesPath() string { if _, err := os.Stat(h.options.ConsoleTemplatesPath + "/index.html"); !os.IsNotExist(err) { return h.options.ExternalURL.Path + "/consoles/index.html" } if h.options.UserAssetsPath != "" { if _, err := os.Stat(h.options.UserAssetsPath + "/index.html"); !os.IsNotExist(err) { return h.options.ExternalURL.Path + "/user/index.html" } } return "" } func tmplFuncs(consolesPath string, opts *Options) template_text.FuncMap { return template_text.FuncMap{ "since": func(t time.Time) time.Duration { return time.Since(t) / time.Millisecond * time.Millisecond }, "consolesPath": func() string { return consolesPath }, "pathPrefix": func() string { return opts.ExternalURL.Path }, "buildVersion": func() string { return opts.Version.Revision }, "stripLabels": func(lset map[string]string, labels ...string) map[string]string { for _, ln := range labels { delete(lset, ln) } return lset }, "globalURL": func(u *url.URL) *url.URL { host, port, err := net.SplitHostPort(u.Host) if err != nil { return u } for _, lhr := range localhostRepresentations { if host == lhr { _, ownPort, err := net.SplitHostPort(opts.ListenAddress) if err != nil { return u } if port == ownPort { // Only in the case where the target is on localhost and its port is // the same as the one we're listening on, we know for sure that // we're monitoring our own process and that we need to change the // scheme, hostname, and port to the externally reachable ones as // well. We shouldn't need to touch the path at all, since if a // path prefix is defined, the path under which we scrape ourselves // should already contain the prefix. u.Scheme = opts.ExternalURL.Scheme u.Host = opts.ExternalURL.Host } else { // Otherwise, we only know that localhost is not reachable // externally, so we replace only the hostname by the one in the // external URL. It could be the wrong hostname for the service on // this port, but it's still the best possible guess. host, _, err := net.SplitHostPort(opts.ExternalURL.Host) if err != nil { return u } u.Host = host + ":" + port } break } } return u }, "numHealthy": func(pool []*retrieval.Target) int { alive := len(pool) for _, p := range pool { if p.Health() != retrieval.HealthGood { alive-- } } return alive }, "healthToClass": func(th retrieval.TargetHealth) string { switch th { case retrieval.HealthUnknown: return "warning" case retrieval.HealthGood: return "success" default: return "danger" } }, "alertStateToClass": func(as rules.AlertState) string { switch as { case rules.StateInactive: return "success" case rules.StatePending: return "warning" case rules.StateFiring: return "danger" default: panic("unknown alert state") } }, } } func (h *Handler) getTemplate(name string) (string, error) { baseTmpl, err := ui.Asset("web/ui/templates/_base.html") if err != nil { return "", fmt.Errorf("error reading base template: %s", err) } pageTmpl, err := ui.Asset(filepath.Join("web/ui/templates", name)) if err != nil { return "", fmt.Errorf("error reading page template %s: %s", name, err) } return string(baseTmpl) + string(pageTmpl), nil } func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data interface{}) { text, err := h.getTemplate(name) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } tmpl := template.NewTemplateExpander( h.context, text, name, data, h.now(), template.QueryFunc(rules.EngineQueryFunc(h.queryEngine)), h.options.ExternalURL, ) tmpl.Funcs(tmplFuncs(h.consolesPath(), h.options)) result, err := tmpl.ExpandHTML(nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } io.WriteString(w, result) } func (h *Handler) dumpHeap(w http.ResponseWriter, r *http.Request) { target := fmt.Sprintf("/tmp/%d.heap", time.Now().Unix()) f, err := os.Create(target) if err != nil { level.Error(h.logger).Log("msg", "Could not dump heap", "err", err) } fmt.Fprintf(w, "Writing to %s...", target) defer f.Close() pprof_runtime.WriteHeapProfile(f) fmt.Fprintf(w, "Done") } // AlertStatus bundles alerting rules and the mapping of alert states to row classes. type AlertStatus struct { AlertingRules []*rules.AlertingRule AlertStateToRowClass map[rules.AlertState]string } type byAlertStateAndNameSorter struct { alerts []*rules.AlertingRule } func (s byAlertStateAndNameSorter) Len() int { return len(s.alerts) } func (s byAlertStateAndNameSorter) Less(i, j int) bool { return s.alerts[i].State() > s.alerts[j].State() || (s.alerts[i].State() == s.alerts[j].State() && s.alerts[i].Name() < s.alerts[j].Name()) } func (s byAlertStateAndNameSorter) Swap(i, j int) { s.alerts[i], s.alerts[j] = s.alerts[j], s.alerts[i] } prometheus-2.1.0+ds/web/web_test.go000066400000000000000000000203551323116307200172320ustar00rootroot00000000000000// Copyright 2016 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package web import ( "context" "fmt" "io/ioutil" "net/http" "net/http/httptest" "net/url" "os" "strings" "testing" "time" "github.com/prometheus/prometheus/storage/tsdb" "github.com/prometheus/prometheus/util/testutil" libtsdb "github.com/prometheus/tsdb" ) func TestMain(m *testing.M) { // On linux with a global proxy the tests will fail as the go client(http,grpc) tries to connect through the proxy. os.Setenv("no_proxy", "localhost,127.0.0.1,0.0.0.0,:") os.Exit(m.Run()) } func TestGlobalURL(t *testing.T) { opts := &Options{ ListenAddress: ":9090", ExternalURL: &url.URL{ Scheme: "https", Host: "externalhost:80", Path: "/path/prefix", }, } tests := []struct { inURL string outURL string }{ { // Nothing should change if the input URL is not on localhost, even if the port is our listening port. inURL: "http://somehost:9090/metrics", outURL: "http://somehost:9090/metrics", }, { // Port and host should change if target is on localhost and port is our listening port. inURL: "http://localhost:9090/metrics", outURL: "https://externalhost:80/metrics", }, { // Only the host should change if the port is not our listening port, but the host is localhost. inURL: "http://localhost:8000/metrics", outURL: "http://externalhost:8000/metrics", }, { // Alternative localhost representations should also work. inURL: "http://127.0.0.1:9090/metrics", outURL: "https://externalhost:80/metrics", }, } for _, test := range tests { inURL, err := url.Parse(test.inURL) testutil.Ok(t, err) globalURL := tmplFuncs("", opts)["globalURL"].(func(u *url.URL) *url.URL) outURL := globalURL(inURL) testutil.Equals(t, test.outURL, outURL.String()) } } func TestReadyAndHealthy(t *testing.T) { t.Parallel() dbDir, err := ioutil.TempDir("", "tsdb-ready") testutil.Ok(t, err) defer os.RemoveAll(dbDir) db, err := libtsdb.Open(dbDir, nil, nil, nil) testutil.Ok(t, err) opts := &Options{ ListenAddress: ":9090", ReadTimeout: 30 * time.Second, MaxConnections: 512, Context: nil, Storage: &tsdb.ReadyStorage{}, QueryEngine: nil, ScrapeManager: nil, RuleManager: nil, Notifier: nil, RoutePrefix: "/", MetricsPath: "/metrics/", EnableAdminAPI: true, TSDB: func() *libtsdb.DB { return db }, } opts.Flags = map[string]string{} webHandler := New(nil, opts) go func() { err := webHandler.Run(context.Background()) if err != nil { panic(fmt.Sprintf("Can't start web handler:%s", err)) } }() // Give some time for the web goroutine to run since we need the server // to be up before starting tests. time.Sleep(5 * time.Second) resp, err := http.Get("http://localhost:9090/-/healthy") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9090/-/ready") testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Get("http://localhost:9090/version") testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Post("http://localhost:9090/api/v2/admin/tsdb/snapshot", "", strings.NewReader("")) testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Post("http://localhost:9090/api/v2/admin/tsdb/delete_series", "", strings.NewReader("{}")) testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) // Set to ready. webHandler.Ready() resp, err = http.Get("http://localhost:9090/-/healthy") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9090/-/ready") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9090/version") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Post("http://localhost:9090/api/v2/admin/tsdb/snapshot", "", strings.NewReader("")) testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Post("http://localhost:9090/api/v2/admin/tsdb/delete_series", "", strings.NewReader("{}")) testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) } func TestRoutePrefix(t *testing.T) { t.Parallel() dbDir, err := ioutil.TempDir("", "tsdb-ready") testutil.Ok(t, err) defer os.RemoveAll(dbDir) db, err := libtsdb.Open(dbDir, nil, nil, nil) testutil.Ok(t, err) opts := &Options{ ListenAddress: ":9091", ReadTimeout: 30 * time.Second, MaxConnections: 512, Context: nil, Storage: &tsdb.ReadyStorage{}, QueryEngine: nil, ScrapeManager: nil, RuleManager: nil, Notifier: nil, RoutePrefix: "/prometheus", MetricsPath: "/prometheus/metrics", EnableAdminAPI: true, TSDB: func() *libtsdb.DB { return db }, } opts.Flags = map[string]string{} webHandler := New(nil, opts) go func() { err := webHandler.Run(context.Background()) if err != nil { panic(fmt.Sprintf("Can't start web handler:%s", err)) } }() // Give some time for the web goroutine to run since we need the server // to be up before starting tests. time.Sleep(5 * time.Second) resp, err := http.Get("http://localhost:9091" + opts.RoutePrefix + "/-/healthy") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/-/ready") testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/version") testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v2/admin/tsdb/snapshot", "", strings.NewReader("")) testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v2/admin/tsdb/delete_series", "", strings.NewReader("{}")) testutil.Ok(t, err) testutil.Equals(t, http.StatusServiceUnavailable, resp.StatusCode) // Set to ready. webHandler.Ready() resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/-/healthy") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/-/ready") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/version") testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v2/admin/tsdb/snapshot", "", strings.NewReader("")) testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v2/admin/tsdb/delete_series", "", strings.NewReader("{}")) testutil.Ok(t, err) testutil.Equals(t, http.StatusOK, resp.StatusCode) } func TestDebugHandler(t *testing.T) { for _, tc := range []struct { prefix, url string code int }{ {"/", "/debug/pprof/cmdline", 200}, {"/foo", "/foo/debug/pprof/cmdline", 200}, {"/", "/debug/pprof/goroutine", 200}, {"/foo", "/foo/debug/pprof/goroutine", 200}, {"/", "/debug/pprof/foo", 404}, {"/foo", "/bar/debug/pprof/goroutine", 404}, } { opts := &Options{ RoutePrefix: tc.prefix, MetricsPath: "/metrics", } handler := New(nil, opts) handler.Ready() w := httptest.NewRecorder() req, err := http.NewRequest("GET", tc.url, nil) testutil.Ok(t, err) handler.router.ServeHTTP(w, req) testutil.Equals(t, tc.code, w.Code) } }