# mtail - extract internal monitoring data from application logs for collection into a timeseries database
[](https://github.com/google/mtail/actions?query=workflow%3ACI+branch%3main)
[](http://godoc.org/github.com/google/mtail)
[](https://goreportcard.com/report/github.com/google/mtail)
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mtail)
[](https://cirrus-ci.com/github/google/mtail)
[](https://codecov.io/gh/google/mtail)
`mtail` is a tool for extracting metrics from application logs to be exported
into a timeseries database or timeseries calculator for alerting and
dashboarding.
It fills a monitoring niche by being the glue between applications that do not
export their own internal state (other than via logs) and existing monitoring
systems, such that system operators do not need to patch those applications to
instrument them or writing custom extraction code for every such application.
The extraction is controlled by [mtail programs](docs/Programming-Guide.md)
which define patterns and actions:
# simple line counter
counter lines_total
/$/ {
lines_total++
}
Metrics are exported for scraping by a collector as JSON or Prometheus format
over HTTP, or can be periodically sent to a collectd, StatsD, or Graphite
collector socket.
Read the [programming guide](docs/Programming-Guide.md) if you want to learn how
to write mtail programs.
Ask general questions on the users mailing list: https://groups.google.com/g/mtail-users
## Installation
There are various ways of installing **mtail**.
### Precompiled binaries
Precompiled binaries for released versions are available in the
[Releases page](https://github.com/google/mtail/releases) on Github. Using the
latest production release binary is the recommended way of installing **mtail**.
Windows, OSX and Linux binaries are available.
### Building from source
The simplest way to get `mtail` is to `go get` it directly.
`go get github.com/google/mtail/cmd/mtail`
This assumes you have a working Go environment with a recent Go version. Usually mtail is tested to work with the last two minor versions (e.g. Go 1.12 and Go 1.11).
If you want to fetch everything, you need to turn on Go Modules to succeed because of the way Go Modules have changed the way go get treats source trees with no Go code at the top level.
```
GO111MODULE=on go get -u github.com/google/mtail
cd $GOPATH/src/github.com/google/mtail
make install
```
If you develop the compiler you will need some additional tools
like `goyacc` to be able to rebuild the parser.
See the [Build instructions](docs/Building.md) for more details.
A `Dockerfile` is included in this repository for local development as an
alternative to installing Go in your environment, and takes care of all the
build dependency installation, if you don't care for that.
## Deployment
`mtail` works best when it paired with a timeseries-based calculator and
alerting tool, like [Prometheus](http://prometheus.io).
> So what you do is you take the metrics from the log files and
> you bring them down to the monitoring system?
[It deals with the instrumentation so the engineers don't have
to!](http://www.imdb.com/title/tt0151804/quotes/qt0386890) It has the
extraction skills! It is good at dealing with log files!!
## Read More
Full documentation at http://google.github.io/mtail/
Read more about writing `mtail` programs:
* [Programming Guide](docs/Programming-Guide.md)
* [Language Reference](docs/Language.md)
* [Metrics](docs/Metrics.md)
* [Managing internal state](docs/state.md)
* [Testing your programs](docs/Testing.md)
Read more about hacking on `mtail`
* [Building from source](docs/Building.md)
* [Contributing](CONTRIBUTING.md)
* [Style](docs/style.md)
Read more about deploying `mtail` and your programs in a monitoring environment
* [Deploying](docs/Deploying.md)
* [Interoperability](docs/Interoperability.md) with other systems
* [Troubleshooting](docs/Troubleshooting.md)
* [FAQ](docs/faq.md)
After that, if you have any questions, please email (and optionally join) the mailing list: https://groups.google.com/forum/#!forum/mtail-users or [file a new issue](https://github.com/google/mtail/issues/new).
mtail-3.0.0~rc48/TODO 0000664 0000000 0000000 00000016020 14121212652 0014202 0 ustar 00root root 0000000 0000000 Implement a standard library, search path:
Means we can provide standard syslog decorator.
Requires figuring out where we keep standard library definitions, and what the syntax for import looks like.
Can't put trailing newlines in cases in parser test, requires changes to expr stmt
parse tree/ast testing? - expected AST as result from parse/check instead of
merely getting a result. A similar version of this is in codegen_test.go:TestCodeGenFromAST
A mapping between progs and logs to reduce wasted processing- issue #35
Means we don't fan out log lines to every VM if reading from multiple sources.
Requires figuring out how to provide this configuration. Special syntax in a program? Not very flexible. A real config file? Been trying to avoid that. Commandline flag? Seems difficult to maintain.
bytecode like
[{push 1} {push 0} {cmp 1}
{jm 6} {push 0} {jmp 7} {push 1} {jnm 13}
{setmatched false} {mload 0} {dload 0} {inc \d{3}) / + # %>s - Status code.
/(?P\S+) / + # %H - The request protocol.
/(?Pconn=.) / + # %X - Connection status when response is completed
/(?P\d+) / + # %D - The time taken to serve the request, in microseconds.
/(?P\d+) / + # %O - Bytes sent, including headers.
/(?P\d+) / + # %I - Bytes received, including request and headers.
/(?P\d+)/ + # %k - Number of keepalive requests handled on this connection.
/$/ {
###
# HTTP Requests with histogram buckets.
#
http_request_duration_seconds[$server_port][$handler][$method][$code][$protocol] = $time_us / 1000000.0
###
# Sent/Received bytes.
http_response_size_bytes_total[$server_port][$handler][$method][$code][$protocol] += $sent_bytes
http_request_size_bytes_total[$server_port][$handler][$method][$code][$protocol] += $received_bytes
### Connection status when response is completed:
# X = Connection aborted before the response completed.
# + = Connection may be kept alive after the response is sent.
# - = Connection will be closed after the response is sent.
/ conn=X / {
http_connections_aborted_total[$server_port][$handler][$method][$code][$protocol][$connection_status]++
}
# Will not include all closed connections. :-(
/ conn=- / {
http_connections_closed_total[$server_port][$handler][$method][$code][$protocol][$connection_status]++
}
}
mtail-3.0.0~rc48/examples/dhcpd.mtail 0000664 0000000 0000000 00000011067 14121212652 0017450 0 ustar 00root root 0000000 0000000 # Copyright 2008 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# Define the exported metric names. The `by' keyword indicates the metric has
# dimensions. For example, `request_total' counts the frequency of each
# request's "command". The name `command' will be exported as the label name
# for the metric. The command provided in the code below will be exported as
# the label value.
counter request_total by command
counter config_file_errors
counter peer_disconnects
counter dhcpdiscovers by mac
counter bind_xid_mismatch
counter duplicate_lease
counter bad_udp_checksum
counter unknown_subnet
counter dhcpdiscover_nofree by network
counter unknown_lease by ip
counter update_rejected
counter failover_peer_timeout
counter ip_already_in_use
counter ip_abandoned by reason
counter invalid_state_transition
counter negative_poolreq by pool
counter lease_conflicts
# The `syslog' decorator defines a procedure. When a block of mtail code is
# "decorated", it is called before entering the block. The block is entered
# when the keyword `next' is reached.
def syslog {
/^(?P(?P\w+\s+\d+\s+\d+:\d+:\d+)|(?P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+[+-]\d{2}:\d{2}))/ +
/\s+(?:\w+@)?(?P[\w\.-]+)\s+(?P[\w\.-]+)(?:\[(?P\d+)\])?:\s+(?P.*)/ {
# If the legacy_date regexp matched, try this format.
len($legacy_date) > 0 {
strptime($2, "Jan _2 15:04:05")
}
# If the RFC3339 style matched, parse it this way.
len($rfc3339_date) > 0 {
strptime($rfc3339_date, "2006-01-02T03:04:05-0700")
}
# Call into the decorated block
next
}
}
# Define some pattern constants for reuse in the patterns below.
const IP /\d+(\.\d+){3}/
const MATCH_IP /(?P/ + IP + /)/
const MATCH_NETWORK /(?P\d+(\.\d+){1,3}\/\d+)/
const MATCH_MAC /(?P([\da-f]{2}:){5}[\da-f]{2})/
@syslog {
# Request
$message =~ /^(balanced|balancing|BOOTREPLY|BOOTREQUEST|DHCPACK|DHCPDECLINE|DHCPDISCOVER|DHCPINFORM|DHCPNAK|DHCPOFFER|DHCPRELEASE|DHCPREQUEST)/ {
# The lowercased name of the command matched in the regex is used to
# count the frequency of each command. An external collector can use
# this to compute the rate of each command independently.
request_total[tolower($1)]++
# DHCP Discover
$message =~ /^DHCPDISCOVER from / + MATCH_MAC {
# Counts the discovery requests per mac address, which can help
# identify bad clients on the network.
dhcpdiscovers[$mac]++
/network / + MATCH_NETWORK + /: no free leases/ {
# If the range is full, your clients may be having a bad time.
dhcpdiscover_nofree[$network]++
}
}
}
# Config file errors
/Configuration file errors encountered -- exiting/ {
# Counting config parse errors can he useful for detecting bad config
# pushes that made it to production.
config_file_errors++
}
# Peer disconnects
/peer ([^:]+): disconnected/ {
peer_disconnects++
}
# XID mismatches
/bind update on / + IP + / got ack from (?P\w+): xid mismatch./ {
bind_xid_mismatch++
}
# Duplicate lease
/uid lease / + MATCH_IP + / for client / + MATCH_MAC + / is duplicate on / + MATCH_NETWORK {
duplicate_lease++
}
# Bad UDP Checksum
/(?P\d+) bad udp checksums in \d+ packets/ {
bad_udp_checksum += $count
}
# Unknown subnet
/DHCPDISCOVER from / + MATCH_MAC + / via / + IP + /: unknown network segment/ {
unknown_subnet++
}
# Unknown lease
/DHCPREQUEST for / + IP + /\(/ + IP + /\) from / + MATCH_MAC + / via / + IP + /: unknown lease / + MATCH_IP {
unknown_lease[$ip]++
}
# Update rejected
/bind update on \S+ from \S+ rejected: incoming update is less critical than the outgoing update/ {
update_rejected++
}
/timeout waiting for failover peer \S+/ {
failover_peer_timeout++
}
/ICMP Echo reply while lease / + IP + /valid/ {
ip_already_in_use++
}
/unexpected ICMP Echo reply from / + IP {
ip_already_in_use++
}
/Abandoning IP address / + IP + /: (?P.*)/ {
ip_abandoned[$reason]++
}
/bind update on \S+ from \S+ rejected: / + IP + /: invalid state transition/ {
invalid_state_transition++
}
/peer (?P[^:]+): Got POOLREQ, answering negatively!/ {
negative_poolreq[$pool]++
}
/Lease conflict at/ {
lease_conflicts++
}
}
mtail-3.0.0~rc48/examples/histogram.mtail 0000664 0000000 0000000 00000002476 14121212652 0020367 0 ustar 00root root 0000000 0000000 # use mtail to extract the values you want in your histogram, and any labels like 'httpcode' and it will create the buckets and histogram metrics for you.
# this example might be something you put on a web server that logs latency. ex;
# GET /foo/bar.html latency=1s httpcode=200
# GET /foo/baz.html latency=0s httpcode=200
# would produce this:
# webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="1"} 1
# webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="2"} 1
# webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="4"} 1
# webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="8"} 1
# webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="+Inf"} 1
# webserver_latency_by_code_sum{httpcode="200",prog="software_errors.mtail"} 1
# webserver_latency_by_code_count{httpcode="200",prog="software_errors.mtail"} 2
#
histogram webserver_latency_by_code by code buckets 0, 1, 2, 4, 8
/latency=(?P\d+)s httpcode=(?P\d+)/ {
webserver_latency_by_code [$httpcode] = $latency
}
# or if you don't need the http code label/dimension furthering the example, just use this
histogram webserver_latency buckets 0, 1, 2, 4, 8
/latency=(?P\d+)/ {
webserver_latency = $latency
}
mtail-3.0.0~rc48/examples/lighttpd.mtail 0000664 0000000 0000000 00000002246 14121212652 0020204 0 ustar 00root root 0000000 0000000 # Copyright 2010 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# mtail module for a lighttpd server
counter request by status
counter time_taken by status
counter bytes_out by subtotal, status
counter bytes_in by status
counter requests by proxy_cache
const ACCESSLOG_RE // +
/(?P\S+) (?P\S+) (?P\S+)/ +
/ \[(?P[^\]]+)\] "(?P\S+) (?P.+?) / +
/(?P\S+)" (?P\d+) (?P\d+) (?P\d+)/ +
/ (?P\d+) (?P\d+) "(?P[^"]+)" / +
/"(?P[^"]+)"/
# /var/log/lighttpd/access.log
getfilename() =~ /lighttpd.access.log/ {
// + ACCESSLOG_RE {
# Parse an accesslog entry.
$url == "/healthz" {
# nothing
}
otherwise {
strptime($access_time, "02/Jan/2006:15:04:05 -0700")
request[$status]++
time_taken[$status] += $time_taken
bytes_out["resp_body", $status] += $bytes_body
bytes_out["resp_header", $status] += $bytes_out - $bytes_body
bytes_in[$status] += $bytes_in
$proxied_for != "-" {
requests[$request_ip]++
}
}
}
}
mtail-3.0.0~rc48/examples/linecount.mtail 0000664 0000000 0000000 00000000333 14121212652 0020360 0 ustar 00root root 0000000 0000000 # Copyright 2011 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# The most basic of mtail programmes -- count the number of lines read.
counter lines_total
/$/ {
lines_total++
}
mtail-3.0.0~rc48/examples/mysql_slowqueries.mtail 0000664 0000000 0000000 00000005036 14121212652 0022174 0 ustar 00root root 0000000 0000000 # Copyright 2008 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# mysql-slowqueries -- mtail module tracking slow mysql queries
hidden text user
hidden text host
hidden text query_type
hidden text service
hidden gauge tmp_query_time
hidden gauge tmp_lock_time
hidden gauge partial
hidden gauge time
counter query_time by type, server, service, user
counter lock_time by type, server, service, user
counter query_time_overall_sum
counter query_time_total_count
counter lock_time_overall_sum
counter lock_time_total_count
# Example lines from the file and regex to match them:
# # User@Host: dbuser[dbuser] @ host [192.0.2.87]
const USER_HOST /^# User@Host: ([a-zA-Z]+)\[[a-zA-Z]+\] @ ([^\. ]+)/
# # Query_time: 30 Lock_time: 0 Rows_sent: 0 Rows_examined: 0
const QUERY_TIME /^# Query_time: (\d+)\s*Lock_time: (\d+)/
# UPDATE ... # outbox;
const FULL_QUERY_LINE /^(INSERT|UPDATE|DELETE|SELECT) .* # (.*);$/
# Not all queries have helpful comments at the end
const UNINSTRUMENTED_QUERY_LINE /^(INSERT|UPDATE|DELETE|SELECT) .*;$/
# If the query gets split up, the service may end up on another line
const PARTIAL_QUERY_LINE /^(INSERT|UPDATE|DELETE|SELECT) .*[^;]$/
# This one has the potential to catch too many things, so it can only be a last
# resort match.
const END_QUERY_LINE /.*;$/
/^# Time: (\d{6} .\d:\d\d:\d\d)/ {
strptime($1, "060102 3:04:05")
time = timestamp()
}
/^SET timestamp=(\d+);/ {
time = $1
}
settime(time)
// + USER_HOST {
user = $1
host = $2
}
# break if no user set yet
user == "" {
stop
}
// + QUERY_TIME {
tmp_query_time = $1
tmp_lock_time = $2
query_time_overall_sum += tmp_query_time
query_time_total_count++
lock_time_overall_sum += tmp_lock_time
lock_time_total_count++
}
// + FULL_QUERY_LINE {
# We should have everything we need now.
query_type = tolower($1)
service = $2
query_time[query_type, host, service, user] += tmp_query_time
lock_time[query_type, host, service, user] += tmp_lock_time
}
// + UNINSTRUMENTED_QUERY_LINE {
# We should have everything we need now.
query_type = tolower($1)
service = "n/a"
query_time[query_type, host, service, user] += tmp_query_time
lock_time[query_type, host, service, user] += tmp_lock_time
}
// + PARTIAL_QUERY_LINE {
query_type = tolower($1)
partial = 1
}
// + END_QUERY_LINE && partial == 1 {
partial = 0
/.*# (.*)$/ {
service = $1
}
otherwise {
service = "n/a"
}
query_time[query_type, host, service, user] += tmp_query_time
lock_time[query_type, host, service, user] += tmp_lock_time
}
mtail-3.0.0~rc48/examples/nocode.mtail 0000664 0000000 0000000 00000000361 14121212652 0017630 0 ustar 00root root 0000000 0000000 # This is an example mtail programme for exporting no code instrumentation
#
# No code has no instrumentation, thus requires an external program to sift
# and export metrics from other sources; in this case with mtail from any log
# files.
mtail-3.0.0~rc48/examples/ntpd.mtail 0000664 0000000 0000000 00000003171 14121212652 0017330 0 ustar 00root root 0000000 0000000 # Copyright 2008 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# Syslog decorator
def syslog {
/^(?P(?P\w+\s+\d+\s+\d+:\d+:\d+)|(?P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+[+-]\d{2}:\d{2}))/ +
/\s+(?:\w+@)?(?P[\w\.-]+)\s+(?P[\w\.-]+)(?:\[(?P\d+)\])?:\s+(?P.*)/ {
len($legacy_date) > 0 {
strptime($2, "Jan _2 15:04:05")
}
len($rfc3339_date) > 0 {
strptime($rfc3339_date, "2006-01-02T03:04:05-0700")
}
next
}
}
@syslog {
counter int_syscalls
/select\(.*\) error: Interrupted system call/ {
int_syscalls++
}
counter recvbuf_overflows
gauge last_recvbuf
/too many recvbufs allocated \((\d+)\)/ {
recvbuf_overflows++
last_recvbuf = $1
}
counter exits
/ntpd exiting on signal 15/ {
exits++
}
counter starts
/x?ntpd .* \w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\w+\s+\d+\s+\(\d\)/ {
starts++
}
gauge sync_status
/kernel time sync (status (change)?|enabled|disabled) (?P\d+)/ {
sync_status = $status
}
# PLL status change.
#
# Described here: http://obswww.unige.ch/~bartho/xntp_faq/faq3Care.htm#araee
counter pll_changes
gauge pll_status
/kernel pll status change (?P\d+)/ {
pll_changes++
pll_status = $status
}
counter peer_syncs
/synchronized to (\d+\.\d+\.\d+\.\d+|LOCAL\(\d\)), stratum(=| )(\d+)/ {
peer_syncs++
}
counter driftfile_errors
/can't open .*drift.*: No such file or directory/ {
driftfile_errors++
}
counter sync_lost_total
/synchronisation lost/ {
sync_lost_total++
}
} # end syslog
mtail-3.0.0~rc48/examples/ntpd_peerstats.mtail 0000664 0000000 0000000 00000002015 14121212652 0021416 0 ustar 00root root 0000000 0000000 # Peerstats log handling
gauge peer_status by peer
gauge peer_select by peer
gauge peer_count by peer
gauge peer_code by peer
gauge peer_offset by peer
gauge peer_delay by peer
gauge peer_dispersion by peer
counter num_peerstats by peer
# TODO(jaq) seconds is int, not float
/^(?P\d+) (?P\d+)\.\d+ (?P\d+\.\d+\.\d+\.\d+) (?P[0-9a-f]+) (?P-?\d+\.\d+) (?P\d+\.\d+) (?P\d+\.\d+)/ {
# Unix epoch in MJD is 40587.
settime(($days - 40587) * 86400 + $seconds)
peer_offset[$peer] = $offset
peer_delay[$peer] = $delay
peer_dispersion[$peer] = $dispersion
# http://www.cis.udel.edu/~mills/ntp/html/decode.html#peer
# bits 0-4
peer_status[$peer] = (strtol($status, 16) >> (16 - 5)) & ((2 ** 5) - 1)
# bits 5-7
peer_select[$peer] = (strtol($status, 16) >> (16 - 8)) & ((2 ** 3) - 1)
# bits 6-11
peer_count[$peer] = (strtol($status, 16) >> (16 - 12)) & ((2 ** 4) - 1)
# bits 12-15
peer_code[$peer] = strtol($status, 16) & ((2 ** 4) - 1)
num_peerstats[$peer]++
}
mtail-3.0.0~rc48/examples/postfix.mtail 0000664 0000000 0000000 00000014623 14121212652 0020063 0 ustar 00root root 0000000 0000000 # vim:ts=2:sw=2:et:ai:sts=2:cinoptions=(0
# Copyright 2017 Martín Ferrari . All Rights Reserved.
# This file is available under the Apache license.
# Syslog parser for Postfix, based on the parsing rules from:
# https://github.com/kumina/postfix_exporter
# Copyright 2017 Kumina, https://kumina.nl/
# Available under the Apache license.
const DELIVERY_DELAY_LINE /.*, relay=(?P\S+), .*,/ +
/ delays=(?P[0-9\.]+)\/(?P[0-9\.]+)\/(?P[0-9\.]+)\/(?P[0-9\.]+),\s/
const SMTP_TLS_LINE /(\S+) TLS connection established to \S+: (\S+) with cipher (\S+) \((\d+)\/(\d+) bits\)/
const SMTPD_TLS_LINE /(\S+) TLS connection established from \S+: (\S+) with cipher (\S+) \((\d+)\/(\d+) bits\)/
const QMGR_INSERT_LINE /:.*, size=(?P\d+), nrcpt=(?P\d+)/
const QMGR_REMOVE_LINE /: removed$/
/^(?P(?P\w+\s+\d+\s+\d+:\d+:\d+)|(?P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+[+-]\d{2}:\d{2}))/ +
/\s+(?:\w+@)?(?P[\w\.-]+)\s+postfix\/(?P[\w\.-\/]+)(?:\[(?P\d+)\])?:\s+(?P.*)/ {
len($legacy_date) > 0 {
strptime($2, "Jan _2 15:04:05")
}
len($rfc3339_date) > 0 {
strptime($rfc3339_date, "2006-01-02T03:04:05-0700")
}
# Total number of messages processed by cleanup.
counter postfix_cleanup_messages_processed_total
# Total number of messages rejected by cleanup.
counter postfix_cleanup_messages_rejected_total
$application == "cleanup" {
/: message-id= {
postfix_cleanup_messages_processed_total++
}
/: reject: / {
postfix_cleanup_messages_rejected_total++
}
}
# LMTP message processing time in seconds.
histogram postfix_lmtp_delivery_delay_seconds by stage buckets 0.001, 0.01, 0.1, 10, 1e2, 1e3
# buckets: 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3
$application == "lmtp" {
// + DELIVERY_DELAY_LINE {
# 1st field: before_queue_manager
postfix_lmtp_delivery_delay_seconds["before_queue_manager"] = $bqm
# 2nd field: queue_manager
postfix_lmtp_delivery_delay_seconds["queue_manager"] = $qm
# 3rd field: connection_setup
postfix_lmtp_delivery_delay_seconds["connection_setup"] = $cs
# 4th field: transmission
postfix_lmtp_delivery_delay_seconds["transmission"] = $tx
}
}
# Pipe message processing time in seconds.
histogram postfix_pipe_delivery_delay_seconds by relay, stage buckets 0.001, 0.01, 0.1, 1, 10, 100, 1e3
# buckets: 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3
$application == "pipe" {
// + DELIVERY_DELAY_LINE {
# 1st field: before_queue_manager
postfix_pipe_delivery_delay_seconds[$relay]["before_queue_manager"] = $bqm
# 2nd field: queue_manager
postfix_pipe_delivery_delay_seconds[$relay]["queue_manager"] = $qm
# 3rd field: connection_setup
postfix_pipe_delivery_delay_seconds[$relay]["connection_setup"] = $cs
# 4th field: transmission
postfix_pipe_delivery_delay_seconds[$relay]["transmission"] = $tx
}
}
# Number of recipients per message inserted into the mail queues.
histogram postfix_qmgr_messages_inserted_recipients buckets 1, 2, 4, 7, 16, 32, 64, 128
# buckets: 1, 2, 4, 8, 16, 32, 64, 128
# Size of messages inserted into the mail queues in bytes.
histogram postfix_qmgr_messages_inserted_size_bytes buckets 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9
# buckets: 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9
# Total number of messages removed from mail queues.
counter postfix_qmgr_messages_removed_total
$application == "qmgr" {
// + QMGR_INSERT_LINE {
postfix_qmgr_messages_inserted_recipients = $nrcpt
postfix_qmgr_messages_inserted_size_bytes = $size
}
// + QMGR_REMOVE_LINE {
postfix_qmgr_messages_removed_total++
}
}
# SMTP message processing time in seconds.
histogram postfix_smtp_delivery_delay_seconds by stage buckets 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3
# buckets: 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3
# Total number of outgoing TLS connections.
counter postfix_smtp_tls_connections_total by trust, protocol, cipher, secret_bits, algorithm_bits
$application == "smtp" {
// + DELIVERY_DELAY_LINE {
# 1st field: before_queue_manager
postfix_smtp_delivery_delay_seconds["before_queue_manager"] = $bqm
# 2nd field: queue_manager
postfix_smtp_delivery_delay_seconds["queue_manager"] = $qm
# 3rd field: connection_setup
postfix_smtp_delivery_delay_seconds["connection_setup"] = $cs
# 4th field: transmission
postfix_smtp_delivery_delay_seconds["transmission"] = $tx
}
// + SMTP_TLS_LINE {
postfix_smtp_tls_connections_total[$1][$2][$3][$4][$5]++
}
}
# Total number of incoming connections.
counter postfix_smtpd_connects_total
# Total number of incoming disconnections.
counter postfix_smtpd_disconnects_total
# Total number of connections for which forward-confirmed DNS cannot be resolved.
counter postfix_smtpd_forward_confirmed_reverse_dns_errors_total
# Total number of connections lost.
counter postfix_smtpd_connections_lost_total by after_stage
# Total number of messages processed.
counter postfix_smtpd_messages_processed_total by sasl_username
# Total number of NOQUEUE rejects.
counter postfix_smtpd_messages_rejected_total by code
# Total number of SASL authentication failures.
counter postfix_smtpd_sasl_authentication_failures_total
# Total number of incoming TLS connections.
counter postfix_smtpd_tls_connections_total by trust, protocol, cipher, secret_bits, algorithm_bits
$application =~ /smtpd/ {
/ connect from / {
postfix_smtpd_connects_total++
}
/ disconnect from / {
postfix_smtpd_disconnects_total++
}
/ warning: hostname \S+ does not resolve to address / {
postfix_smtpd_forward_confirmed_reverse_dns_errors_total++
}
/ lost connection after (\w+) from / {
postfix_smtpd_connections_lost_total[$1]++
}
/: client=/ {
/, sasl_username=(\S+)/ {
postfix_smtpd_messages_processed_total[$1]++
} else {
postfix_smtpd_messages_processed_total[""]++
}
}
/NOQUEUE: reject: RCPT from \S+: (\d+) / {
postfix_smtpd_messages_rejected_total[$1]++
}
/warning: \S+: SASL \S+ authentication failed: / {
postfix_smtpd_sasl_authentication_failures_total++
}
// + SMTPD_TLS_LINE {
postfix_smtpd_tls_connections_total[$1][$2][$3][$4][$5]++
}
}
}
mtail-3.0.0~rc48/examples/rails.mtail 0000664 0000000 0000000 00000002167 14121212652 0017501 0 ustar 00root root 0000000 0000000 # Copyright 2017 Pablo Carranza . All Rights Reserved.
# This file is available under the Apache license.
#
# Rails production log parsing
counter rails_requests_started_total
counter rails_requests_started by verb
counter rails_requests_completed_total
counter rails_requests_completed by status
histogram rails_requests_completed_seconds by status buckets 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 15.0
/^Started (?P[A-Z]+) .*/ {
###
# Started HTTP requests by verb (GET, POST, etc.)
#
rails_requests_started_total++
rails_requests_started[$verb]++
}
/^Completed (?P\d{3}) .+ in (?P\d+)ms .*$/ {
###
# Total numer of completed requests by status
#
rails_requests_completed_total++
rails_requests_completed[$status]++
###
# Completed requests by status with histogram buckets
#
# These statements "fall through", so the histogram is cumulative. The
# collecting system can compute the percentile bands by taking the ratio of
# each bucket value over the final bucket.
rails_requests_completed_seconds[$status] = $request_seconds / 1000.0
}
mtail-3.0.0~rc48/examples/rsyncd.mtail 0000664 0000000 0000000 00000003221 14121212652 0017661 0 ustar 00root root 0000000 0000000 # Copyright 2011 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
counter bytes_total by operation
# total connections, and total connection time can be used to compute the
# average connection time.
counter connections_total
counter connection_time_total as "connection-time_total"
# See which modules are popular.
counter transfers_total by operation, module
# Use this gauge to measure duration between start and end time per connection.
# It is never used externally, so mark as `hidden'.
hidden gauge connection_time by pid
/^(?P\d+\/\d+\/\d+ \d+:\d+:\d+) \[(?P\d+)\] / {
strptime($date, "2006/01/02 15:04:05")
# Transfer log
# %o %h [%a] %m (%u) %f %l
/(?P\S+) (\S+) \[\S+\] (?P\S+) \(\S*\) \S+ (?P\d+)/ {
transfers_total[$operation, $module]++
}
# Connection starts
/connect from \S+ \(\d+\.\d+\.\d+\.\d+\)/ {
connections_total++
# Record the start time of the connection, using the log timestamp.
connection_time[$pid] = timestamp()
}
# Connection summary when session closed
/sent (?P\d+) bytes received (?P\d+) bytes total size \d+/ {
# Sum total bytes across all sessions for this process
bytes_total["sent"] += $sent
bytes_total["received"] += $received
# Count total time spent with connections open, according to the log timestamp.
connection_time_total += timestamp() - connection_time[$pid]
# Delete the datum referenced in this dimensional metric. We assume that
# this will never happen again, and hint to the VM that we can garbage
# collect the memory used.
del connection_time[$pid]
}
}
mtail-3.0.0~rc48/examples/sftp.mtail 0000664 0000000 0000000 00000002246 14121212652 0017341 0 ustar 00root root 0000000 0000000 # Copyright 2008 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
counter login_count by username
counter logout_count by username
counter bytes_read
counter files_read
counter bytes_written
counter files_written
counter user_bytes_read by username
counter user_files_read by username
counter user_bytes_written by username
counter user_files_written by username
/^(?P\w+\s+\d+\s+\d+:\d+:\d+)\s+[\w\.-]+\s+sftp-server/ {
strptime($date, "Jan _2 15:04:05")
/session opened for local user (?P\w+)/ {
login_count[$username]++
}
/session closed for local user (?P\w+)/ {
logout_count[$username]++
}
/close "[^"]+" bytes read (?P\d+) written (?P\d+)/ {
$read != 0 {
bytes_read += $read
files_read++
}
$written != 0 {
bytes_written += $written
files_written++
}
/close "\/home\/(?P[^\/]+)\/[^"]+"/ {
$read != 0 {
user_bytes_read[$username] += $read
user_files_read[$username]++
}
$written != 0 {
user_bytes_written[$username] += $written
user_files_written[$username]++
}
}
}
}
mtail-3.0.0~rc48/examples/timer.mtail 0000664 0000000 0000000 00000000336 14121212652 0017503 0 ustar 00root root 0000000 0000000 # `timer` is the same as gauge but has special meaning for statsd export.
# Otherwise just use a gauge.
timer request_time_ms by vhost
/(?P\S+) (?P\d+)/ {
request_time_ms[$vhost] = $latency_s / 1000
}
mtail-3.0.0~rc48/examples/vsftpd.mtail 0000664 0000000 0000000 00000005410 14121212652 0017667 0 ustar 00root root 0000000 0000000 # Copyright 2011 Google Inc. All Rights Reserved.
# This file is available under the Apache license.
# A mtail module for monitoring vsftpd logs
#
# Configure your vsftpd to write the xferlog as well as vsftpd.log
hidden text direction
counter bytes_transferred by direction
counter transfer_time by direction
counter transfers by direction
counter connects
counter logins
counter uploads
counter commands by command
counter responses by response
hidden gauge sessions by client
counter session_time
def vsftpd_timestamp {
# Mon Feb 21 15:21:32 2011
/^\w+\s(?P\w+\s+\d+\s\d+:\d+:\d+\s\d+)/ {
strptime($date, "Jan _2 15:04:05 2006")
next
}
}
const XFERLOG_RE // +
# e.g. 1 172.18.115.36 528
# time spent transferring
/\s(?P\d+)/ +
# remote host
/\s\d+\.\d+\.\d+\.\d+/ +
# bytes transferred
/\s(?P\d+)/ +
# filename
/\s(?P\S+)/ +
# e.g. b _ i a anonymous@ ftp 0 * c
# transfertype
/\s\S/ +
# special action flag
/\s\S/ +
# direction
/\s(?P\S)/ +
# access mode
/\s\S/ +
# username
/\s\S+/ +
# service name
/\s\S+/ +
# authentication method
/\s\d/ +
# authenticated id
/\s\S+/ +
# completion status
/\s(?P\S)/
const VSFTPD_LOG_RE // +
/ \[pid \d+\]/ +
/( \[\w+\])?/ +
/ (?PCONNECT|OK LOGIN|OK UPLOAD|FTP (command|response)):/ +
/ Client "(?P\d+\.\d+\.\d+\.\d+)"/ +
/(, (?P.*))?/
const PAYLOAD_RESPONSE_RE /^"(\d{3})[" -]/
const PAYLOAD_COMMAND_RE /^"(\w{4})[" -]/
@vsftpd_timestamp {
getfilename() =~ /xferlog/ {
// + XFERLOG_RE {
# Handles log entries from the wuftpd format xferlog.
$direction == "i" {
direction = "incoming"
}
$direction == "o" {
direction = "outgoing"
}
$completionstatus == "c" {
transfers[direction]++
}
transfer_time[direction] += $transfertime
bytes_transferred[direction] += $bytestransferred
}
}
getfilename() =~ /vsftpd.log/ {
// + VSFTPD_LOG_RE {
# Handle vsftpd.log log file."""
$command == "CONNECT" {
sessions[$client] = timestamp()
del sessions[$client] after 168h
connects++
}
$command == "OK LOGIN" {
logins++
}
$command == "OK UPLOAD" {
uploads++
}
$command == "FTP command" {
$payload =~ // + PAYLOAD_COMMAND_RE {
commands[$1]++
$1 == "QUIT" {
session_time += timestamp() - sessions[$client]
del sessions[$client]
}
}
}
$command == "FTP response" {
$payload =~ // + PAYLOAD_RESPONSE_RE {
responses[$1]++
}
}
}
}
}
mtail-3.0.0~rc48/go.mod 0000664 0000000 0000000 00000000703 14121212652 0014621 0 ustar 00root root 0000000 0000000 module github.com/google/mtail
go 1.16
require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/google/go-cmp v0.5.6
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/common v0.30.0
go.opencensus.io v0.23.0
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40
)
mtail-3.0.0~rc48/go.sum 0000664 0000000 0000000 00000135247 14121212652 0014662 0 ustar 00root root 0000000 0000000 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
contrib.go.opencensus.io/exporter/jaeger v0.2.1 h1:yGBYzYMewVL0yO9qqJv3Z5+IRhPdU7e9o/2oKpX4YvI=
contrib.go.opencensus.io/exporter/jaeger v0.2.1/go.mod h1:Y8IsLgdxqh1QxYxPC5IgXVmBaeLUeQFfBeBi9PbeZd0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
mtail-3.0.0~rc48/hooks/ 0000775 0000000 0000000 00000000000 14121212652 0014636 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/hooks/build 0000775 0000000 0000000 00000000660 14121212652 0015665 0 ustar 00root root 0000000 0000000 #!/bin/bash
# $IMAGE_NAME var is injected into the build so the tag is correct.
echo "Build hook running"
docker build \
--build-arg version=$(git describe --tags --always) \
--build-arg commit_hash=$(git rev-parse HEAD) \
--build-arg vcs_url=$(git config --get remote.origin.url) \
--build-arg vcs_branch=$(git rev-parse --abbrev-ref HEAD) \
--build-arg build_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
-t $IMAGE_NAME .
mtail-3.0.0~rc48/hooks/post_checkout 0000775 0000000 0000000 00000000152 14121212652 0017434 0 ustar 00root root 0000000 0000000 #!/bin/bash
echo "Unshallowing to get correct tags to work."
git fetch --tags --unshallow --quiet origin
mtail-3.0.0~rc48/hooks/post_push 0000775 0000000 0000000 00000002105 14121212652 0016606 0 ustar 00root root 0000000 0000000 #!/bin/bash
# hooks/post_push
# https://docs.docker.com/docker-cloud/builds/advanced/
# https://semver.org/
function add_tag() {
echo "Adding tag ${1}"
docker tag $IMAGE_NAME $DOCKER_REPO:$1
docker push $DOCKER_REPO:$1
}
TAG=`git describe --tag --match "v*"`
MAJOR=`echo ${TAG} | awk -F'-' '{print $1}' | awk -F'.' '{print $1}' | sed 's/v//'`
MINOR=`echo ${TAG} | awk -F'-' '{print $1}' | awk -F'.' '{print $2}' | sed 's/v//'`
PATCH=`echo ${TAG} | awk -F'-' '{print $1}' | awk -F'.' '{print $3}' | sed 's/v//'`
PRLS=`echo ${TAG} | awk -F'-' '{print $2}'`
num='^[0-9]+$'
pre='^[0-9A-Za-z\.]+$'
echo "Current Build: ${TAG}"
if [ ! -z $MAJOR ] && [[ $MAJOR =~ $num ]]; then
add_tag ${MAJOR}
if [ ! -z $MINOR ] && [[ $MINOR =~ $num ]]; then
add_tag ${MAJOR}.${MINOR}
if [ ! -z $PATCH ] && [[ $PATCH =~ $num ]]; then
add_tag ${MAJOR}.${MINOR}.${PATCH}
if [ ! -z $PRLS ] && [[ ! $PRLS =~ $num ]] && [[ $PRLS =~ $pre ]]; then
add_tag ${MAJOR}.${MINOR}.${PATCH}-${PRLS}
fi
fi
fi
fi
exit $?
mtail-3.0.0~rc48/internal/ 0000775 0000000 0000000 00000000000 14121212652 0015327 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/exporter/ 0000775 0000000 0000000 00000000000 14121212652 0017177 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/exporter/collectd.go 0000664 0000000 0000000 00000002515 14121212652 0021322 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"expvar"
"flag"
"fmt"
"strings"
"time"
"github.com/google/mtail/internal/metrics"
)
const (
// See https://collectd.org/wiki/index.php/Plain_text_protocol#PUTVAL
collectdFormat = "PUTVAL \"%s/%smtail-%s/%s-%s\" interval=%d %s:%s\n"
)
var (
collectdSocketPath = flag.String("collectd_socketpath", "",
"Path to collectd unixsock to write metrics to.")
collectdPrefix = flag.String("collectd_prefix", "",
"Prefix to use for collectd metrics.")
collectdExportTotal = expvar.NewInt("collectd_export_total")
collectdExportSuccess = expvar.NewInt("collectd_export_success")
)
// metricToCollectd encodes the metric data in the collectd text protocol format. The
// metric lock is held before entering this function.
func metricToCollectd(hostname string, m *metrics.Metric, l *metrics.LabelSet, interval time.Duration) string {
return fmt.Sprintf(collectdFormat,
hostname,
*collectdPrefix,
m.Program,
kindToCollectdType(m.Kind),
formatLabels(m.Name, l.Labels, "-", "-", "_"),
int64(interval.Seconds()),
l.Datum.TimeString(),
l.Datum.ValueString())
}
func kindToCollectdType(kind metrics.Kind) string {
if kind != metrics.Timer {
return strings.ToLower(kind.String())
}
return "gauge"
}
mtail-3.0.0~rc48/internal/exporter/export.go 0000664 0000000 0000000 00000014713 14121212652 0021055 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
// Package exporter provides the interface for getting metrics out of mtail,
// into your monitoring system of choice.
package exporter
import (
"context"
"expvar"
"flag"
"fmt"
"io"
"net"
"os"
"strings"
"sync"
"time"
"github.com/golang/glog"
"github.com/google/mtail/internal/metrics"
"github.com/pkg/errors"
)
// Commandline Flags.
var (
writeDeadline = flag.Duration("metric_push_write_deadline", 10*time.Second, "Time to wait for a push to succeed before exiting with an error.")
)
// Exporter manages the export of metrics to passive and active collectors.
type Exporter struct {
ctx context.Context
wg sync.WaitGroup
store *metrics.Store
pushInterval time.Duration
hostname string
omitProgLabel bool
emitTimestamp bool
pushTargets []pushOptions
initDone chan struct{}
}
// Option configures a new Exporter.
type Option func(*Exporter) error
// Hostname specifies the mtail hostname to use in exported metrics.
func Hostname(hostname string) Option {
return func(e *Exporter) error {
e.hostname = hostname
return nil
}
}
// OmitProgLabel sets the Exporter to not put program names in metric labels.
func OmitProgLabel() Option {
return func(e *Exporter) error {
e.omitProgLabel = true
return nil
}
}
// EmitTimestamp instructs the exporter to send metric's timestamps to collectors.
func EmitTimestamp() Option {
return func(e *Exporter) error {
e.emitTimestamp = true
return nil
}
}
func PushInterval(opt time.Duration) Option {
return func(e *Exporter) error {
e.pushInterval = opt
return nil
}
}
// New creates a new Exporter.
func New(ctx context.Context, wg *sync.WaitGroup, store *metrics.Store, options ...Option) (*Exporter, error) {
if store == nil {
return nil, errors.New("exporter needs a Store")
}
e := &Exporter{
ctx: ctx,
store: store,
initDone: make(chan struct{}),
}
defer close(e.initDone)
if err := e.SetOption(options...); err != nil {
return nil, err
}
// defaults after options have been set
if e.hostname == "" {
var err error
e.hostname, err = os.Hostname()
if err != nil {
return nil, errors.Wrap(err, "getting hostname")
}
}
if *collectdSocketPath != "" {
o := pushOptions{"unix", *collectdSocketPath, metricToCollectd, collectdExportTotal, collectdExportSuccess}
e.RegisterPushExport(o)
}
if *graphiteHostPort != "" {
o := pushOptions{"tcp", *graphiteHostPort, metricToGraphite, graphiteExportTotal, graphiteExportSuccess}
e.RegisterPushExport(o)
}
if *statsdHostPort != "" {
o := pushOptions{"udp", *statsdHostPort, metricToStatsd, statsdExportTotal, statsdExportSuccess}
e.RegisterPushExport(o)
}
e.StartMetricPush()
// This routine manages shutdown of the Exporter. TODO(jaq): This doesn't
// happen before mtail returns because of how context cancellation is set
// up.. How can we tie this shutdown in before mtail exits? Should
// exporter be merged with httpserver?
go func() {
<-e.initDone
<-e.ctx.Done()
e.wg.Wait()
}()
return e, nil
}
// SetOption takes one or more option functions and applies them in order to Exporter.
func (e *Exporter) SetOption(options ...Option) error {
for _, option := range options {
if err := option(e); err != nil {
return err
}
}
return nil
}
// formatLabels converts a metric name and key-value map of labels to a single
// string for exporting to the correct output format for each export target.
// ksep and sep mark what to use for key/val separator, and between label separators respoectively.
// If not empty, rep is used to replace cases of ksep and sep in the original strings.
func formatLabels(name string, m map[string]string, ksep, sep, rep string) string {
r := name
if len(m) > 0 {
var s []string
for k, v := range m {
k1 := strings.ReplaceAll(strings.ReplaceAll(k, ksep, rep), sep, rep)
v1 := strings.ReplaceAll(strings.ReplaceAll(v, ksep, rep), sep, rep)
s = append(s, fmt.Sprintf("%s%s%s", k1, ksep, v1))
}
return r + sep + strings.Join(s, sep)
}
return r
}
// Format a LabelSet into a string to be written to one of the timeseries
// sockets.
type formatter func(string, *metrics.Metric, *metrics.LabelSet, time.Duration) string
func (e *Exporter) writeSocketMetrics(c io.Writer, f formatter, exportTotal *expvar.Int, exportSuccess *expvar.Int) error {
return e.store.Range(func(m *metrics.Metric) error {
m.RLock()
// Don't try to send text metrics to any push service.
if m.Kind == metrics.Text {
m.RUnlock()
return nil
}
exportTotal.Add(1)
lc := make(chan *metrics.LabelSet)
go m.EmitLabelSets(lc)
for l := range lc {
line := f(e.hostname, m, l, e.pushInterval)
n, err := fmt.Fprint(c, line)
glog.V(2).Infof("Sent %d bytes\n", n)
if err == nil {
exportSuccess.Add(1)
} else {
return errors.Errorf("write error: %s\n", err)
}
}
m.RUnlock()
return nil
})
}
// PushMetrics sends metrics to each of the configured services.
func (e *Exporter) PushMetrics() {
for _, target := range e.pushTargets {
glog.V(2).Infof("pushing to %s", target.addr)
conn, err := net.DialTimeout(target.net, target.addr, *writeDeadline)
if err != nil {
glog.Infof("pusher dial error: %s", err)
continue
}
err = conn.SetDeadline(time.Now().Add(*writeDeadline))
if err != nil {
glog.Infof("Couldn't set deadline on connection: %s", err)
}
err = e.writeSocketMetrics(conn, target.f, target.total, target.success)
if err != nil {
glog.Infof("pusher write error: %s", err)
}
err = conn.Close()
if err != nil {
glog.Infof("connection close failed: %s", err)
}
}
}
// StartMetricPush pushes metrics to the configured services each interval.
func (e *Exporter) StartMetricPush() {
if len(e.pushTargets) == 0 {
return
}
if e.pushInterval <= 0 {
return
}
e.wg.Add(1)
go func() {
defer e.wg.Done()
<-e.initDone
glog.Info("Started metric push.")
ticker := time.NewTicker(e.pushInterval)
defer ticker.Stop()
for {
select {
case <-e.ctx.Done():
return
case <-ticker.C:
e.PushMetrics()
}
}
}()
}
type pushOptions struct {
net, addr string
f formatter
total, success *expvar.Int
}
// RegisterPushExport adds a push export connection to the Exporter. Items in
// the list must describe a Dial()able connection and will have all the metrics
// pushed to each pushInterval.
func (e *Exporter) RegisterPushExport(p pushOptions) {
e.pushTargets = append(e.pushTargets, p)
}
mtail-3.0.0~rc48/internal/exporter/export_test.go 0000664 0000000 0000000 00000015210 14121212652 0022105 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"context"
"errors"
"reflect"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
const prefix = "prefix"
func TestCreateExporter(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
_, err := New(ctx, &wg, nil)
if err == nil {
t.Error("expecting error, got nil")
}
cancel()
wg.Wait()
ctx, cancel = context.WithCancel(context.Background())
store := metrics.NewStore()
_, err = New(ctx, &wg, store)
if err != nil {
t.Errorf("unexpected error:%s", err)
}
cancel()
wg.Wait()
ctx, cancel = context.WithCancel(context.Background())
failopt := func(*Exporter) error {
return errors.New("busted") // nolint:goerr113
}
_, err = New(ctx, &wg, store, failopt)
if err == nil {
t.Errorf("unexpected success")
}
cancel()
wg.Wait()
}
func FakeSocketWrite(f formatter, m *metrics.Metric) []string {
ret := make([]string, 0)
lc := make(chan *metrics.LabelSet)
d := 60 * time.Second
go m.EmitLabelSets(lc)
for l := range lc {
ret = append(ret, f("gunstar", m, l, d))
}
sort.Strings(ret)
return ret
}
func TestMetricToCollectd(t *testing.T) {
*collectdPrefix = ""
ts, terr := time.Parse("2006/01/02 15:04:05", "2012/07/24 10:14:00")
if terr != nil {
t.Errorf("time parse error: %s", terr)
}
ms := metrics.NewStore()
scalarMetric := metrics.NewMetric("foo", "prog", metrics.Counter, metrics.Int)
d, _ := scalarMetric.GetDatum()
datum.SetInt(d, 37, ts)
testutil.FatalIfErr(t, ms.Add(scalarMetric))
r := FakeSocketWrite(metricToCollectd, scalarMetric)
expected := []string{"PUTVAL \"gunstar/mtail-prog/counter-foo\" interval=60 1343124840:37\n"}
testutil.ExpectNoDiff(t, expected, r)
dimensionedMetric := metrics.NewMetric("bar", "prog", metrics.Gauge, metrics.Int, "label")
d, _ = dimensionedMetric.GetDatum("quux")
datum.SetInt(d, 37, ts)
d, _ = dimensionedMetric.GetDatum("snuh")
datum.SetInt(d, 37, ts)
ms.ClearMetrics()
testutil.FatalIfErr(t, ms.Add(dimensionedMetric))
r = FakeSocketWrite(metricToCollectd, dimensionedMetric)
expected = []string{
"PUTVAL \"gunstar/mtail-prog/gauge-bar-label-quux\" interval=60 1343124840:37\n",
"PUTVAL \"gunstar/mtail-prog/gauge-bar-label-snuh\" interval=60 1343124840:37\n",
}
testutil.ExpectNoDiff(t, expected, r)
timingMetric := metrics.NewMetric("foo", "prog", metrics.Timer, metrics.Int)
d, _ = timingMetric.GetDatum()
datum.SetInt(d, 123, ts)
testutil.FatalIfErr(t, ms.Add(timingMetric))
r = FakeSocketWrite(metricToCollectd, timingMetric)
expected = []string{"PUTVAL \"gunstar/mtail-prog/gauge-foo\" interval=60 1343124840:123\n"}
testutil.ExpectNoDiff(t, expected, r)
*collectdPrefix = prefix
r = FakeSocketWrite(metricToCollectd, timingMetric)
expected = []string{"PUTVAL \"gunstar/prefixmtail-prog/gauge-foo\" interval=60 1343124840:123\n"}
testutil.ExpectNoDiff(t, expected, r)
}
func TestMetricToGraphite(t *testing.T) {
*graphitePrefix = ""
ts, terr := time.Parse("2006/01/02 15:04:05", "2012/07/24 10:14:00")
if terr != nil {
t.Errorf("time parse error: %s", terr)
}
scalarMetric := metrics.NewMetric("foo", "prog", metrics.Counter, metrics.Int)
d, _ := scalarMetric.GetDatum()
datum.SetInt(d, 37, ts)
r := FakeSocketWrite(metricToGraphite, scalarMetric)
expected := []string{"prog.foo 37 1343124840\n"}
testutil.ExpectNoDiff(t, expected, r)
dimensionedMetric := metrics.NewMetric("bar", "prog", metrics.Gauge, metrics.Int, "host")
d, _ = dimensionedMetric.GetDatum("quux.com")
datum.SetInt(d, 37, ts)
d, _ = dimensionedMetric.GetDatum("snuh.teevee")
datum.SetInt(d, 37, ts)
r = FakeSocketWrite(metricToGraphite, dimensionedMetric)
expected = []string{
"prog.bar.host.quux_com 37 1343124840\n",
"prog.bar.host.snuh_teevee 37 1343124840\n",
}
testutil.ExpectNoDiff(t, expected, r)
histogramMetric := metrics.NewMetric("hist", "prog", metrics.Histogram, metrics.Buckets, "xxx")
lv := &metrics.LabelValue{Labels: []string{"bar"}, Value: datum.MakeBuckets([]datum.Range{{0, 10}, {10, 20}}, time.Unix(0, 0))}
histogramMetric.AppendLabelValue(lv)
d, _ = histogramMetric.GetDatum("bar")
datum.SetFloat(d, 1, ts)
datum.SetFloat(d, 5, ts)
datum.SetFloat(d, 15, ts)
datum.SetFloat(d, 12, ts)
datum.SetFloat(d, 19, ts)
datum.SetFloat(d, 1000, ts)
r = FakeSocketWrite(metricToGraphite, histogramMetric)
r = strings.Split(strings.TrimSuffix(r[0], "\n"), "\n")
sort.Strings(r)
expected = []string{
"prog.hist.xxx.bar 1052 1343124840",
"prog.hist.xxx.bar.bin_10 2 1343124840",
"prog.hist.xxx.bar.bin_20 3 1343124840",
"prog.hist.xxx.bar.bin_inf 1 1343124840",
"prog.hist.xxx.bar.count 6 1343124840",
}
testutil.ExpectNoDiff(t, expected, r)
*graphitePrefix = prefix
r = FakeSocketWrite(metricToGraphite, dimensionedMetric)
expected = []string{
"prefixprog.bar.host.quux_com 37 1343124840\n",
"prefixprog.bar.host.snuh_teevee 37 1343124840\n",
}
testutil.ExpectNoDiff(t, expected, r)
}
func TestMetricToStatsd(t *testing.T) {
*statsdPrefix = ""
ts, terr := time.Parse("2006/01/02 15:04:05", "2012/07/24 10:14:00")
if terr != nil {
t.Errorf("time parse error: %s", terr)
}
scalarMetric := metrics.NewMetric("foo", "prog", metrics.Counter, metrics.Int)
d, _ := scalarMetric.GetDatum()
datum.SetInt(d, 37, ts)
r := FakeSocketWrite(metricToStatsd, scalarMetric)
expected := []string{"prog.foo:37|c"}
if !reflect.DeepEqual(expected, r) {
t.Errorf("String didn't match:\n\texpected: %v\n\treceived: %v", expected, r)
}
dimensionedMetric := metrics.NewMetric("bar", "prog", metrics.Gauge, metrics.Int, "l")
d, _ = dimensionedMetric.GetDatum("quux")
datum.SetInt(d, 37, ts)
d, _ = dimensionedMetric.GetDatum("snuh")
datum.SetInt(d, 42, ts)
r = FakeSocketWrite(metricToStatsd, dimensionedMetric)
expected = []string{
"prog.bar.l.quux:37|g",
"prog.bar.l.snuh:42|g",
}
if !reflect.DeepEqual(expected, r) {
t.Errorf("String didn't match:\n\texpected: %v\n\treceived: %v", expected, r)
}
timingMetric := metrics.NewMetric("foo", "prog", metrics.Timer, metrics.Int)
d, _ = timingMetric.GetDatum()
datum.SetInt(d, 37, ts)
r = FakeSocketWrite(metricToStatsd, timingMetric)
expected = []string{"prog.foo:37|ms"}
if !reflect.DeepEqual(expected, r) {
t.Errorf("String didn't match:\n\texpected: %v\n\treceived: %v", expected, r)
}
*statsdPrefix = prefix
r = FakeSocketWrite(metricToStatsd, timingMetric)
expected = []string{"prefixprog.foo:37|ms"}
if !reflect.DeepEqual(expected, r) {
t.Errorf("prefixed string didn't match:\n\texpected: %v\n\treceived: %v", expected, r)
}
}
mtail-3.0.0~rc48/internal/exporter/graphite.go 0000664 0000000 0000000 00000004443 14121212652 0021336 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"expvar"
"flag"
"fmt"
"math"
"net/http"
"strings"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
)
var (
graphiteHostPort = flag.String("graphite_host_port", "",
"Host:port to graphite carbon server to write metrics to.")
graphitePrefix = flag.String("graphite_prefix", "",
"Prefix to use for graphite metrics.")
graphiteExportTotal = expvar.NewInt("graphite_export_total")
graphiteExportSuccess = expvar.NewInt("graphite_export_success")
)
func (e *Exporter) HandleGraphite(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-type", "text/plain")
err := e.store.Range(func(m *metrics.Metric) error {
select {
case <-r.Context().Done():
return r.Context().Err()
default:
}
m.RLock()
graphiteExportTotal.Add(1)
lc := make(chan *metrics.LabelSet)
go m.EmitLabelSets(lc)
for l := range lc {
line := metricToGraphite(e.hostname, m, l, 0)
fmt.Fprint(w, line)
}
m.RUnlock()
return nil
})
if err != nil {
http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError)
}
}
// metricToGraphite encodes a metric in the graphite text protocol format. The
// metric lock is held before entering this function.
func metricToGraphite(hostname string, m *metrics.Metric, l *metrics.LabelSet, _ time.Duration) string {
var b strings.Builder
if m.Kind == metrics.Histogram && m.Type == metrics.Buckets {
d := m.LabelValues[0].Value
buckets := datum.GetBuckets(d)
for r, c := range buckets.GetBuckets() {
var binName string
if math.IsInf(r.Max, 1) {
binName = "inf"
} else {
binName = fmt.Sprintf("%v", r.Max)
}
fmt.Fprintf(&b, "%s%s.%s.bin_%s %v %v\n",
*graphitePrefix,
m.Program,
formatLabels(m.Name, l.Labels, ".", ".", "_"),
binName,
c,
l.Datum.TimeString())
}
fmt.Fprintf(&b, "%s%s.%s.count %v %v\n",
*graphitePrefix,
m.Program,
formatLabels(m.Name, l.Labels, ".", ".", "_"),
buckets.GetCount(),
l.Datum.TimeString())
}
fmt.Fprintf(&b, "%s%s.%s %v %v\n",
*graphitePrefix,
m.Program,
formatLabels(m.Name, l.Labels, ".", ".", "_"),
l.Datum.ValueString(),
l.Datum.TimeString())
return b.String()
}
mtail-3.0.0~rc48/internal/exporter/graphite_test.go 0000664 0000000 0000000 00000003160 14121212652 0022370 0 ustar 00root root 0000000 0000000 // Copyright 2021 Adam Romanek
// This file is available under the Apache license.
package exporter
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
var handleGraphiteTests = []struct {
name string
metrics []*metrics.Metric
expected string
}{
{
"empty",
[]*metrics.Metric{},
"",
},
{
"single",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
"foobar.test.foo 1 0\n",
},
}
func TestHandleGraphite(t *testing.T) {
*graphitePrefix = "foobar."
for _, tc := range handleGraphiteTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
ms := metrics.NewStore()
for _, metric := range tc.metrics {
testutil.FatalIfErr(t, ms.Add(metric))
}
e, err := New(ctx, &wg, ms, Hostname("gunstar"))
testutil.FatalIfErr(t, err)
response := httptest.NewRecorder()
e.HandleGraphite(response, &http.Request{})
if response.Code != 200 {
t.Errorf("response code not 200: %d", response.Code)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Errorf("failed to read response %s", err)
}
testutil.ExpectNoDiff(t, tc.expected, string(b), testutil.IgnoreUnexported(sync.RWMutex{}))
cancel()
wg.Wait()
})
}
}
mtail-3.0.0~rc48/internal/exporter/json.go 0000664 0000000 0000000 00000001436 14121212652 0020503 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"encoding/json"
"expvar"
"net/http"
"github.com/golang/glog"
)
var exportJSONErrors = expvar.NewInt("exporter_json_errors")
// HandleJSON exports the metrics in JSON format via HTTP.
func (e *Exporter) HandleJSON(w http.ResponseWriter, r *http.Request) {
b, err := json.MarshalIndent(e.store, "", " ")
if err != nil {
exportJSONErrors.Add(1)
glog.Info("error marshalling metrics into json:", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("content-type", "application/json")
if _, err := w.Write(b); err != nil {
glog.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
mtail-3.0.0~rc48/internal/exporter/json_test.go 0000664 0000000 0000000 00000006063 14121212652 0021543 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"context"
"io/ioutil"
"math"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
var handleJSONTests = []struct {
name string
metrics []*metrics.Metric
expected string
}{
{
"empty",
[]*metrics.Metric{},
"[]",
},
{
"single",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`[
{
"Name": "foo",
"Program": "test",
"Kind": 1,
"Type": 0,
"LabelValues": [
{
"Value": {
"Value": 1,
"Time": 0
}
}
]
}
]`,
},
{
"dimensioned",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
Keys: []string{"a", "b"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"1", "2"}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`[
{
"Name": "foo",
"Program": "test",
"Kind": 1,
"Type": 0,
"Keys": [
"a",
"b"
],
"LabelValues": [
{
"Labels": [
"1",
"2"
],
"Value": {
"Value": 1,
"Time": 0
}
}
]
}
]`,
},
{
"histogram",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Histogram,
Keys: []string{"a", "b"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"1", "2"}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
Buckets: []datum.Range{{Min: 0, Max: math.Inf(1)}},
},
},
`[
{
"Name": "foo",
"Program": "test",
"Kind": 5,
"Type": 0,
"Keys": [
"a",
"b"
],
"LabelValues": [
{
"Labels": [
"1",
"2"
],
"Value": {
"Value": 1,
"Time": 0
}
}
],
"Buckets": [
{
"Min": "0",
"Max": "+Inf"
}
]
}
]`,
},
}
func TestHandleJSON(t *testing.T) {
for _, tc := range handleJSONTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
ms := metrics.NewStore()
for _, metric := range tc.metrics {
testutil.FatalIfErr(t, ms.Add(metric))
}
e, err := New(ctx, &wg, ms, Hostname("gunstar"))
testutil.FatalIfErr(t, err)
response := httptest.NewRecorder()
e.HandleJSON(response, &http.Request{})
if response.Code != 200 {
t.Errorf("response code not 200: %d", response.Code)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Errorf("failed to read response: %s", err)
}
testutil.ExpectNoDiff(t, tc.expected, string(b), testutil.IgnoreUnexported(sync.RWMutex{}))
cancel()
wg.Wait()
})
}
}
mtail-3.0.0~rc48/internal/exporter/prometheus.go 0000664 0000000 0000000 00000006641 14121212652 0021730 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"expvar"
"fmt"
"io"
"strings"
"github.com/golang/glog"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/expfmt"
)
var metricExportTotal = expvar.NewInt("metric_export_total")
func noHyphens(s string) string {
return strings.ReplaceAll(s, "-", "_")
}
// Describe implements the prometheus.Collector interface.
func (e *Exporter) Describe(c chan<- *prometheus.Desc) {
prometheus.DescribeByCollect(e, c)
}
// Collect implements the prometheus.Collector interface.
func (e *Exporter) Collect(c chan<- prometheus.Metric) {
lastMetric := ""
lastSource := ""
/* #nosec G104 always retursn nil */
e.store.Range(func(m *metrics.Metric) error {
m.RLock()
// We don't have a way of converting text metrics to prometheus format.
if m.Kind == metrics.Text {
m.RUnlock()
return nil
}
metricExportTotal.Add(1)
lsc := make(chan *metrics.LabelSet)
go m.EmitLabelSets(lsc)
for ls := range lsc {
if lastMetric != m.Name {
glog.V(2).Infof("setting source to %s", m.Source)
lastSource = m.Source
lastMetric = m.Name
}
var keys []string
var vals []string
if !e.omitProgLabel {
keys = append(keys, "prog")
vals = append(vals, m.Program)
}
for k, v := range ls.Labels {
keys = append(keys, k)
vals = append(vals, v)
}
var pM prometheus.Metric
var err error
if m.Kind == metrics.Histogram {
pM, err = prometheus.NewConstHistogram(
prometheus.NewDesc(noHyphens(m.Name),
fmt.Sprintf("defined at %s", lastSource), keys, nil),
datum.GetBucketsCount(ls.Datum),
datum.GetBucketsSum(ls.Datum),
datum.GetBucketsCumByMax(ls.Datum),
vals...)
} else {
pM, err = prometheus.NewConstMetric(
prometheus.NewDesc(noHyphens(m.Name),
fmt.Sprintf("defined at %s", lastSource), keys, nil),
promTypeForKind(m.Kind),
promValueForDatum(ls.Datum),
vals...)
}
if err != nil {
glog.Warning(err)
return nil
}
// By default no timestamp is emitted to Prometheus. Setting a
// timestamp is not recommended. It can lead to unexpected results
// if the timestamp is not updated or moved fowarded enough to avoid
// triggering Promtheus staleness handling.
// Read more in docs/faq.md
if e.emitTimestamp {
c <- prometheus.NewMetricWithTimestamp(ls.Datum.TimeUTC(), pM)
} else {
c <- pM
}
}
m.RUnlock()
return nil
})
}
// Write is used to write Prometheus metrics to an io.Writer.
func (e *Exporter) Write(w io.Writer) error {
reg := prometheus.NewRegistry()
err := reg.Register(e)
if err != nil {
return err
}
mfs, err := reg.Gather()
if err != nil {
return err
}
enc := expfmt.NewEncoder(w, expfmt.FmtText)
for _, mf := range mfs {
err := enc.Encode(mf)
if err != nil {
return err
}
}
return nil
}
func promTypeForKind(k metrics.Kind) prometheus.ValueType {
switch k {
case metrics.Counter:
return prometheus.CounterValue
case metrics.Gauge:
return prometheus.GaugeValue
case metrics.Timer:
return prometheus.GaugeValue
}
return prometheus.UntypedValue
}
func promValueForDatum(d datum.Datum) float64 {
switch n := d.(type) {
case *datum.Int:
return float64(n.Get())
case *datum.Float:
return n.Get()
}
return 0.
}
mtail-3.0.0~rc48/internal/exporter/prometheus_test.go 0000664 0000000 0000000 00000017046 14121212652 0022770 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"bytes"
"context"
"math"
"strings"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
promtest "github.com/prometheus/client_golang/prometheus/testutil"
)
var handlePrometheusTests = []struct {
name string
progLabel bool
metrics []*metrics.Metric
expected string
}{
{
"empty",
false,
[]*metrics.Metric{},
"",
},
{
"single",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo counter
foo{} 1
`,
},
{
"with prog label",
true,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo counter
foo{prog="test"} 1
`,
},
{
"dimensioned",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
Keys: []string{"a", "b"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"1", "2"}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo counter
foo{a="1",b="2"} 1
`,
},
{
"gauge",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Gauge,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo gauge
foo{} 1
`,
},
{
"timer",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Timer,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo gauge
foo{} 1
`,
},
{
"text",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Text,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeString("hi", time.Unix(0, 0))}},
},
},
"",
},
{
"quotes",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
Keys: []string{"a"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"str\"bang\"blah"}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo counter
foo{a="str\"bang\"blah"} 1
`,
},
{
"help",
false,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
Source: "location.mtail:37",
},
},
`# HELP foo defined at location.mtail:37
# TYPE foo counter
foo{} 1
`,
},
{
"2 help with label",
true,
[]*metrics.Metric{
{
Name: "foo",
Program: "test2",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
Source: "location.mtail:37",
},
{
Name: "foo",
Program: "test1",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
Source: "different.mtail:37",
},
},
`# HELP foo defined at location.mtail:37
# TYPE foo counter
foo{prog="test2"} 1
foo{prog="test1"} 1
`,
},
{
"histo",
true,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Histogram,
Keys: []string{"a"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"bar"}, Value: datum.MakeBuckets([]datum.Range{{0, 1}, {1, 2}}, time.Unix(0, 0))}},
Source: "location.mtail:37",
},
},
`# HELP foo defined at location.mtail:37
# TYPE foo histogram
foo_bucket{a="bar",prog="test",le="1"} 0
foo_bucket{a="bar",prog="test",le="2"} 0
foo_bucket{a="bar",prog="test",le="+Inf"} 0
foo_sum{a="bar",prog="test"} 0
foo_count{a="bar",prog="test"} 0
`,
},
{
"histo-count-eq-inf",
true,
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Histogram,
Keys: []string{"a"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"bar"},
Value: &datum.Buckets{
Buckets: []datum.BucketCount{
{
Range: datum.Range{Min: 0, Max: 1},
Count: 1,
},
{
Range: datum.Range{Min: 1, Max: 2},
Count: 1,
},
{
Range: datum.Range{Min: 2, Max: math.Inf(+1)},
Count: 2,
},
},
Count: 4,
Sum: 5,
},
},
},
Source: "location.mtail:37",
},
},
`# HELP foo defined at location.mtail:37
# TYPE foo histogram
foo_bucket{a="bar",prog="test",le="1"} 1
foo_bucket{a="bar",prog="test",le="2"} 2
foo_bucket{a="bar",prog="test",le="+Inf"} 4
foo_sum{a="bar",prog="test"} 5
foo_count{a="bar",prog="test"} 4
`,
},
}
func TestHandlePrometheus(t *testing.T) {
for _, tc := range handlePrometheusTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
ms := metrics.NewStore()
for _, metric := range tc.metrics {
testutil.FatalIfErr(t, ms.Add(metric))
}
opts := []Option{
Hostname("gunstar"),
}
if !tc.progLabel {
opts = append(opts, OmitProgLabel())
}
e, err := New(ctx, &wg, ms, opts...)
testutil.FatalIfErr(t, err)
r := strings.NewReader(tc.expected)
if err = promtest.CollectAndCompare(e, r); err != nil {
t.Error(err)
}
cancel()
wg.Wait()
})
}
}
var writePrometheusTests = []struct {
name string
metrics []*metrics.Metric
expected string
}{
{
"empty",
[]*metrics.Metric{},
"",
},
{
"single",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
},
`# HELP foo defined at
# TYPE foo counter
foo 1
`,
},
{
"multi",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(0, 0))}},
},
{
Name: "bar",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(2, time.Unix(0, 0))}},
},
},
`# HELP bar defined at
# TYPE bar counter
bar 2
# HELP foo defined at
# TYPE foo counter
foo 1
`,
},
}
func TestWritePrometheus(t *testing.T) {
for _, tc := range writePrometheusTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
ms := metrics.NewStore()
for _, metric := range tc.metrics {
testutil.FatalIfErr(t, ms.Add(metric))
}
opts := []Option{
Hostname("gunstar"),
OmitProgLabel(),
}
e, err := New(ctx, &wg, ms, opts...)
testutil.FatalIfErr(t, err)
var buf bytes.Buffer
err = e.Write(&buf)
testutil.FatalIfErr(t, err)
testutil.ExpectNoDiff(t, tc.expected, buf.String())
cancel()
wg.Wait()
})
}
}
mtail-3.0.0~rc48/internal/exporter/statsd.go 0000664 0000000 0000000 00000002111 14121212652 0021023 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"expvar"
"flag"
"fmt"
"time"
"github.com/google/mtail/internal/metrics"
)
var (
statsdHostPort = flag.String("statsd_hostport", "",
"Host:port to statsd server to write metrics to.")
statsdPrefix = flag.String("statsd_prefix", "",
"Prefix to use for statsd metrics.")
statsdExportTotal = expvar.NewInt("statsd_export_total")
statsdExportSuccess = expvar.NewInt("statsd_export_success")
)
// metricToStatsd encodes a metric in the statsd text protocol format. The
// metric lock is held before entering this function.
func metricToStatsd(hostname string, m *metrics.Metric, l *metrics.LabelSet, _ time.Duration) string {
var t string
switch m.Kind {
case metrics.Counter:
t = "c" // StatsD Counter
case metrics.Gauge:
t = "g" // StatsD Gauge
case metrics.Timer:
t = "ms" // StatsD Timer
}
return fmt.Sprintf("%s%s.%s:%s|%s",
*statsdPrefix,
m.Program,
formatLabels(m.Name, l.Labels, ".", ".", "_"),
l.Datum.ValueString(), t)
}
mtail-3.0.0~rc48/internal/exporter/varz.go 0000664 0000000 0000000 00000002626 14121212652 0020516 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"expvar"
"fmt"
"net/http"
"sort"
"strings"
"github.com/google/mtail/internal/metrics"
)
var exportVarzTotal = expvar.NewInt("exporter_varz_total")
const varzFormat = "%s{%s} %s\n"
// HandleVarz exports the metrics in Varz format via HTTP.
func (e *Exporter) HandleVarz(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-type", "text/plain")
err := e.store.Range(func(m *metrics.Metric) error {
select {
case <-r.Context().Done():
return r.Context().Err()
default:
}
m.RLock()
exportVarzTotal.Add(1)
lc := make(chan *metrics.LabelSet)
go m.EmitLabelSets(lc)
for l := range lc {
line := metricToVarz(m, l, e.omitProgLabel, e.hostname)
fmt.Fprint(w, line)
}
m.RUnlock()
return nil
})
if err != nil {
http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError)
}
}
func metricToVarz(m *metrics.Metric, l *metrics.LabelSet, omitProgLabel bool, hostname string) string {
s := make([]string, 0, len(l.Labels)+2)
for k, v := range l.Labels {
s = append(s, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(s)
if !omitProgLabel {
s = append(s, fmt.Sprintf("prog=%s", m.Program))
}
s = append(s, fmt.Sprintf("instance=%s", hostname))
return fmt.Sprintf(varzFormat,
m.Name,
strings.Join(s, ","),
l.Datum.ValueString())
}
mtail-3.0.0~rc48/internal/exporter/varz_test.go 0000664 0000000 0000000 00000004256 14121212652 0021556 0 ustar 00root root 0000000 0000000 // Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package exporter
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
var handleVarzTests = []struct {
name string
metrics []*metrics.Metric
expected string
}{
{
"empty",
[]*metrics.Metric{},
"",
},
{
"single",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeInt(1, time.Unix(1397586900, 0))}},
},
},
`foo{prog=test,instance=gunstar} 1
`,
},
{
"dimensioned",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Counter,
Keys: []string{"a", "b"},
LabelValues: []*metrics.LabelValue{{Labels: []string{"1", "2"}, Value: datum.MakeInt(1, time.Unix(1397586900, 0))}},
},
},
`foo{a=1,b=2,prog=test,instance=gunstar} 1
`,
},
{
"text",
[]*metrics.Metric{
{
Name: "foo",
Program: "test",
Kind: metrics.Text,
LabelValues: []*metrics.LabelValue{{Labels: []string{}, Value: datum.MakeString("hi", time.Unix(1397586900, 0))}},
},
},
`foo{prog=test,instance=gunstar} hi
`,
},
}
func TestHandleVarz(t *testing.T) {
for _, tc := range handleVarzTests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
ms := metrics.NewStore()
for _, metric := range tc.metrics {
testutil.FatalIfErr(t, ms.Add(metric))
}
e, err := New(ctx, &wg, ms, Hostname("gunstar"))
testutil.FatalIfErr(t, err)
response := httptest.NewRecorder()
e.HandleVarz(response, &http.Request{})
if response.Code != 200 {
t.Errorf("response code not 200: %d", response.Code)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Errorf("failed to read response: %s", err)
}
testutil.ExpectNoDiff(t, tc.expected, string(b))
cancel()
wg.Wait()
})
}
}
mtail-3.0.0~rc48/internal/logline/ 0000775 0000000 0000000 00000000000 14121212652 0016760 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/logline/logline.go 0000664 0000000 0000000 00000001046 14121212652 0020741 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package logline
import "context"
// LogLine contains all the information about a line just read from a log.
type LogLine struct {
Context context.Context
Filename string // The log filename that this line was read from
Line string // The text of the log line itself up to the newline.
}
// New creates a new LogLine object.
func New(ctx context.Context, filename string, line string) *LogLine {
return &LogLine{ctx, filename, line}
}
mtail-3.0.0~rc48/internal/metrics/ 0000775 0000000 0000000 00000000000 14121212652 0016775 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/metrics/datum/ 0000775 0000000 0000000 00000000000 14121212652 0020107 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/metrics/datum/buckets.go 0000664 0000000 0000000 00000003534 14121212652 0022103 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"encoding/json"
"fmt"
"strconv"
"sync"
"sync/atomic"
"time"
)
type Range struct {
Min float64
Max float64
}
type BucketCount struct {
Range Range
Count uint64
}
func (r *Range) Contains(v float64) bool {
return r.Min < v && v <= r.Max
}
// Buckets describes a floating point value at a given timestamp.
type Buckets struct {
BaseDatum
sync.RWMutex
Buckets []BucketCount
Count uint64
Sum float64
}
func (d *Buckets) ValueString() string {
return fmt.Sprintf("%g", d.GetSum())
}
func (d *Buckets) Observe(v float64, ts time.Time) {
d.Lock()
defer d.Unlock()
for i, b := range d.Buckets {
if b.Range.Contains(v) {
d.Buckets[i].Count++
break
}
}
d.Count++
d.Sum += v
d.stamp(ts)
}
func (d *Buckets) GetCount() uint64 {
d.RLock()
defer d.RUnlock()
return d.Count
}
func (d *Buckets) GetSum() float64 {
d.RLock()
defer d.RUnlock()
return d.Sum
}
func (d *Buckets) AddBucket(r Range) {
d.Lock()
defer d.Unlock()
d.Buckets = append(d.Buckets, BucketCount{r, 0})
}
func (d *Buckets) GetBuckets() map[Range]uint64 {
d.RLock()
defer d.RUnlock()
b := make(map[Range]uint64)
for _, bc := range d.Buckets {
b[bc.Range] = bc.Count
}
return b
}
func (d *Buckets) MarshalJSON() ([]byte, error) {
d.RLock()
defer d.RUnlock()
bs := make(map[string]uint64)
for _, b := range d.Buckets {
bs[strconv.FormatFloat(b.Range.Max, 'g', -1, 64)] = b.Count
}
j := struct {
Buckets map[string]uint64
Count uint64
Sum float64
Time int64
}{bs, d.Count, d.Sum, atomic.LoadInt64(&d.Time)}
return json.Marshal(j)
}
func (r *Range) MarshalJSON() ([]byte, error) {
j := struct {
Min string
Max string
}{fmt.Sprintf("%v", r.Min), fmt.Sprintf("%v", r.Max)}
return json.Marshal(j)
}
mtail-3.0.0~rc48/internal/metrics/datum/buckets_test.go 0000664 0000000 0000000 00000002166 14121212652 0023142 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum_test
import (
"math"
"testing"
"testing/quick"
"time"
"github.com/google/mtail/internal/metrics/datum"
)
func TestBucketContains(t *testing.T) {
if err := quick.Check(func(min, max, val float64) bool {
r := &datum.Range{Min: min, Max: max}
truth := val < max && val >= min
return truth == r.Contains(val)
}, nil); err != nil {
t.Error(err)
}
}
func TestMakeBucket(t *testing.T) {
r := []datum.Range{
{0, 1},
{1, 2},
{2, 4},
}
b := datum.MakeBuckets(r, time.Unix(37, 42))
ts := time.Unix(37, 31)
datum.Observe(b, 2, ts)
if r := datum.GetBucketsSum(b); r != 2 {
t.Errorf("sum not 2, got %v", r)
}
if r := datum.GetBucketsCount(b); r != 1 {
t.Errorf("count not 1, got %v", r)
}
bs := datum.GetBucketsCumByMax(b)
if r := datum.GetBucketsCount(b); r != bs[math.Inf(+1)] {
t.Errorf("Inf bucket des not equal total observation count: %v vs %v", bs[math.Inf(+1)], r)
}
if len(bs) != len(r)+1 {
t.Errorf("missing buckets from BucketsByMax: expected %d, got %v", len(r)+1, len(bs))
}
}
mtail-3.0.0~rc48/internal/metrics/datum/datum.go 0000664 0000000 0000000 00000014365 14121212652 0021561 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"fmt"
"math"
"sort"
"sync/atomic"
"time"
)
// Datum is an interface for metric datums, with a type, value and timestamp to be exported.
type Datum interface {
// // Type returns the Datum type.
// Type() metrics.Type
// ValueString returns the value of a Datum as a string.
ValueString() string
// TimeString returns the timestamp of a Datum as a string.
TimeString() string
// Time returns the timestamp of the Datum as time.Time in UTC
TimeUTC() time.Time
}
// BaseDatum is a struct used to record timestamps across all Datum implementations.
type BaseDatum struct {
Time int64 // nanoseconds since unix epoch
}
var zeroTime time.Time
func (d *BaseDatum) stamp(timestamp time.Time) {
if timestamp.IsZero() {
atomic.StoreInt64(&d.Time, time.Now().UTC().UnixNano())
} else {
atomic.StoreInt64(&d.Time, timestamp.UnixNano())
}
}
// TimeString returns the timestamp of this Datum as a string.
func (d *BaseDatum) TimeString() string {
return fmt.Sprintf("%d", atomic.LoadInt64(&d.Time)/1e9)
}
func (d *BaseDatum) TimeUTC() time.Time {
tNsec := atomic.LoadInt64(&d.Time)
return time.Unix(tNsec/1e9, tNsec%1e9)
}
// NewInt creates a new zero integer datum.
func NewInt() Datum {
return MakeInt(0, zeroTime)
}
// NewFloat creates a new zero floating-point datum.
func NewFloat() Datum {
return MakeFloat(0., zeroTime)
}
// NewString creates a new zero string datum.
func NewString() Datum {
return MakeString("", zeroTime)
}
// NewBuckets creates a new zero buckets datum.
func NewBuckets(buckets []Range) Datum {
return MakeBuckets(buckets, zeroTime)
}
// MakeInt creates a new integer datum with the provided value and timestamp.
func MakeInt(v int64, ts time.Time) Datum {
d := &Int{}
d.Set(v, ts)
return d
}
// MakeFloat creates a new floating-point datum with the provided value and timestamp.
func MakeFloat(v float64, ts time.Time) Datum {
d := &Float{}
d.Set(v, ts)
return d
}
// MakeString creates a new string datum with the provided value and timestamp.
func MakeString(v string, ts time.Time) Datum {
d := &String{}
d.Set(v, ts)
return d
}
// MakeBuckets creates a new bucket datum with the provided list of ranges and
// timestamp. If no +inf bucket is provided, one is created.
func MakeBuckets(buckets []Range, ts time.Time) Datum {
d := &Buckets{}
seenInf := false
highest := 0.0
for _, b := range buckets {
d.AddBucket(b)
if math.IsInf(b.Max, +1) {
seenInf = true
} else if b.Max > highest {
highest = b.Max
}
}
if !seenInf {
d.AddBucket(Range{highest, math.Inf(+1)})
}
return d
}
// GetInt returns the integer value of a datum, or error.
func GetInt(d Datum) int64 {
switch d := d.(type) {
case *Int:
return d.Get()
default:
panic(fmt.Sprintf("datum %v is not an Int", d))
}
}
// GetFloat returns the floating-point value of a datum, or error.
func GetFloat(d Datum) float64 {
switch d := d.(type) {
case *Float:
return d.Get()
default:
panic(fmt.Sprintf("datum %v is not a Float", d))
}
}
// GetString returns the string of a datum, or error.
func GetString(d Datum) string {
switch d := d.(type) {
case *String:
return d.Get()
default:
panic(fmt.Sprintf("datum %v is not a String", d))
}
}
// SetInt sets an integer datum to the provided value and timestamp, or panics if the Datum is not an IntDatum.
func SetInt(d Datum, v int64, ts time.Time) {
switch d := d.(type) {
case *Int:
d.Set(v, ts)
case *Buckets:
d.Observe(float64(v), ts)
default:
panic(fmt.Sprintf("datum %v is not an Int", d))
}
}
// SetFloat sets a floating-point Datum to the provided value and timestamp, or panics if the Datum is not a FloatDatum.
func SetFloat(d Datum, v float64, ts time.Time) {
switch d := d.(type) {
case *Float:
d.Set(v, ts)
case *Buckets:
d.Observe(v, ts)
default:
panic(fmt.Sprintf("datum %v is not a Float", d))
}
}
// SetString sets a string Datum to the provided value and timestamp, or panics if the Datym is not a String Datum.
func SetString(d Datum, v string, ts time.Time) {
switch d := d.(type) {
case *String:
d.Set(v, ts)
default:
panic(fmt.Sprintf("datum %v is not a String", d))
}
}
// IncIntBy increments an integer Datum by the provided value, at time ts, or panics if the Datum is not an IntDatum.
func IncIntBy(d Datum, v int64, ts time.Time) {
switch d := d.(type) {
case *Int:
d.IncBy(v, ts)
default:
panic(fmt.Sprintf("datum %v is not an Int", d))
}
}
// DecIntBy increments an integer Datum by the provided value, at time ts, or panics if the Datum is not an IntDatum.
func DecIntBy(d Datum, v int64, ts time.Time) {
switch d := d.(type) {
case *Int:
d.DecBy(v, ts)
default:
panic(fmt.Sprintf("datum %v is not an Int", d))
}
}
func GetBuckets(d Datum) *Buckets {
switch d := d.(type) {
case *Buckets:
return d
default:
panic(fmt.Sprintf("datum %v is not a Buckets", d))
}
}
// Observe records an observation v at time ts in d, or panics if d is not a BucketsDatum.
func Observe(d Datum, v float64, ts time.Time) {
switch d := d.(type) {
case *Buckets:
d.Observe(v, ts)
default:
panic(fmt.Sprintf("datum %v is not a Buckets", d))
}
}
// GetBucketCount returns the total count of observations in d, or panics if d is not a BucketsDatum.
func GetBucketsCount(d Datum) uint64 {
switch d := d.(type) {
case *Buckets:
return d.GetCount()
default:
panic(fmt.Sprintf("datum %v is not a Buckets", d))
}
}
// GetBucketsSum returns the sum of observations in d, or panics if d is not a BucketsDatum.
func GetBucketsSum(d Datum) float64 {
switch d := d.(type) {
case *Buckets:
return d.GetSum()
default:
panic(fmt.Sprintf("datum %v is not a Buckets", d))
}
}
// GetBucketsCumByMax returns a map of cumulative bucket observations by their
// upper bonds, or panics if d is not a BucketsDatum.
func GetBucketsCumByMax(d Datum) map[float64]uint64 {
switch d := d.(type) {
case *Buckets:
buckets := make(map[float64]uint64)
maxes := make([]float64, 0)
for r, c := range d.GetBuckets() {
maxes = append(maxes, r.Max)
buckets[r.Max] = c
}
sort.Float64s(maxes)
cum := uint64(0)
for _, m := range maxes {
cum += buckets[m]
buckets[m] = cum
}
return buckets
default:
panic(fmt.Sprintf("datum %v is not a Buckets", d))
}
}
mtail-3.0.0~rc48/internal/metrics/datum/datum_test.go 0000664 0000000 0000000 00000002554 14121212652 0022615 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"encoding/json"
"testing"
"time"
"github.com/google/mtail/internal/testutil"
)
func TestDatumSetAndValue(t *testing.T) {
d := MakeInt(12, time.Unix(37, 42))
if r := GetInt(d); r != 12 {
t.Errorf("d ditn't return 12, got %v", r)
}
if r := d.ValueString(); r != "12" {
t.Errorf("d value is not 12, got %v", r)
}
if r := d.TimeString(); r != "37" {
t.Errorf("d Time not correct, got %v", r)
}
d = MakeFloat(1.2, time.Unix(37, 42))
if r := GetFloat(d); r != 1.2 {
t.Errorf("d ditn't return 12, got %v", r)
}
if r := d.ValueString(); r != "1.2" {
t.Errorf("d value is not 12, got %v", r)
}
if r := d.TimeString(); r != "37" {
t.Errorf("d Time not correct, got %v", r)
}
}
var datumJSONTests = []struct {
datum Datum
expected string
}{
{
MakeInt(37, time.Unix(42, 12)),
`{"Value":37,"Time":42000000012}`,
},
{
MakeFloat(37.1, time.Unix(42, 12)),
`{"Value":37.1,"Time":42000000012}`,
},
}
func TestMarshalJSON(t *testing.T) {
// This is not a round trip test because only the LabelValue knows how to unmarshal a Datum.
for i, tc := range datumJSONTests {
b, err := json.Marshal(tc.datum)
if err != nil {
t.Errorf("%d: Marshal failed: %v", i, err)
}
testutil.ExpectNoDiff(t, tc.expected, string(b))
}
}
mtail-3.0.0~rc48/internal/metrics/datum/float.go 0000664 0000000 0000000 00000001675 14121212652 0021554 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"encoding/json"
"fmt"
"math"
"sync/atomic"
"time"
)
// Float describes a floating point value at a given timestamp.
type Float struct {
BaseDatum
Valuebits uint64
}
// ValueString returns the value of the Float as a string.
func (d *Float) ValueString() string {
return fmt.Sprintf("%g", d.Get())
}
// Set sets value of the Float at the timestamp ts.
func (d *Float) Set(v float64, ts time.Time) {
atomic.StoreUint64(&d.Valuebits, math.Float64bits(v))
d.stamp(ts)
}
// Get returns the floating-point value.
func (d *Float) Get() float64 {
return math.Float64frombits(atomic.LoadUint64(&d.Valuebits))
}
// MarshalJSON returns a JSON encoding of the Float.
func (d *Float) MarshalJSON() ([]byte, error) {
j := struct {
Value float64
Time int64
}{d.Get(), atomic.LoadInt64(&d.Time)}
return json.Marshal(j)
}
mtail-3.0.0~rc48/internal/metrics/datum/int.go 0000664 0000000 0000000 00000002404 14121212652 0021230 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"encoding/json"
"fmt"
"sync/atomic"
"time"
)
// Int describes an integer value at a given timestamp.
type Int struct {
BaseDatum
Value int64
}
// Set sets the value of the Int to the value at timestamp.
func (d *Int) Set(value int64, timestamp time.Time) {
atomic.StoreInt64(&d.Value, value)
d.stamp(timestamp)
}
// IncBy increments the Int's value by the value provided, at timestamp.
func (d *Int) IncBy(delta int64, timestamp time.Time) {
atomic.AddInt64(&d.Value, delta)
d.stamp(timestamp)
}
// DecBy increments the Int's value by the value provided, at timestamp.
func (d *Int) DecBy(delta int64, timestamp time.Time) {
atomic.AddInt64(&d.Value, -delta)
d.stamp(timestamp)
}
// Get returns the value of the Int.
func (d *Int) Get() int64 {
return atomic.LoadInt64(&d.Value)
}
// ValueString returns the value of the Int as a string.
func (d *Int) ValueString() string {
return fmt.Sprintf("%d", atomic.LoadInt64(&d.Value))
}
// MarshalJSON returns a JSON encoding of the Int.
func (d *Int) MarshalJSON() ([]byte, error) {
j := struct {
Value int64
Time int64
}{d.Get(), atomic.LoadInt64(&d.Time)}
return json.Marshal(j)
}
mtail-3.0.0~rc48/internal/metrics/datum/int_test.go 0000664 0000000 0000000 00000001236 14121212652 0022271 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"testing"
"time"
)
func BenchmarkIncrementScalarInt(b *testing.B) {
d := &Int{}
ts := time.Now().UTC()
for i := 0; i < b.N; i++ {
d.IncBy(1, ts)
}
}
func BenchmarkDecrementScalarInt(b *testing.B) {
d := &Int{}
ts := time.Now().UTC()
for i := 0; i < b.N; i++ {
d.DecBy(1, ts)
}
}
func TestDecrementScalarInt(t *testing.T) {
d := &Int{}
ts := time.Now().UTC()
d.IncBy(1, ts)
r := d.Get()
if r != 1 {
t.Errorf("expected 1, got %d", r)
}
d.DecBy(1, ts)
r = d.Get()
if r != 0 {
t.Errorf("expected 0, got %d", r)
}
}
mtail-3.0.0~rc48/internal/metrics/datum/string.go 0000664 0000000 0000000 00000001670 14121212652 0021750 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package datum
import (
"encoding/json"
"sync"
"sync/atomic"
"time"
)
// String describes a string value at a given timestamp.
type String struct {
BaseDatum
mu sync.RWMutex
Value string
}
// Set sets the value of the String to the value at timestamp.
func (d *String) Set(value string, timestamp time.Time) {
d.mu.Lock()
d.Value = value
d.stamp(timestamp)
d.mu.Unlock()
}
// Get returns the value of the String.
func (d *String) Get() string {
d.mu.RLock()
defer d.mu.RUnlock()
return d.Value
}
// ValueString returns the value of the String as a string.
func (d *String) ValueString() string {
return d.Get()
}
// MarshalJSON returns a JSON encoding of the String.
func (d *String) MarshalJSON() ([]byte, error) {
j := struct {
Value string
Time int64
}{d.Get(), atomic.LoadInt64(&d.Time)}
return json.Marshal(j)
}
mtail-3.0.0~rc48/internal/metrics/metric.go 0000664 0000000 0000000 00000017120 14121212652 0020610 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
// Package metrics provides storage for metrics being recorded by mtail
// programs.
package metrics
import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
"strings"
"sync"
"time"
"github.com/google/mtail/internal/metrics/datum"
"github.com/pkg/errors"
)
// Kind enumerates the types of metrics supported.
type Kind int
const (
_ Kind = iota
// Counter is a monotonically nondecreasing metric.
Counter
// Gauge is a Kind that can take on any value, and may be set
// discontinuously from its previous value.
Gauge
// Timer is a specialisation of Gauge that can be used to store time
// intervals, such as latency and durations. It enables certain behaviour
// in exporters that handle time intervals such as StatsD.
Timer
// Text is a special metric type for free text, usually for operating as a 'hidden' metric, as often these values cannot be exported.
Text
// Histogram is a Kind that observes a value and stores the value
// in a bucket.
Histogram
endKind // end of enumeration for testing
)
func (m Kind) String() string {
switch m {
case Counter:
return "Counter"
case Gauge:
return "Gauge"
case Timer:
return "Timer"
case Text:
return "Text"
case Histogram:
return "Histogram"
}
return "Unknown"
}
// Generate implements the quick.Generator interface for Kind.
func (Kind) Generate(rand *rand.Rand, size int) reflect.Value {
return reflect.ValueOf(Kind(rand.Intn(int(endKind))))
}
// LabelValue is an object that names a Datum value with a list of label
// strings.
type LabelValue struct {
Labels []string `json:",omitempty"`
Value datum.Datum
// After this time of inactivity, the LabelValue is removed from the metric.
Expiry time.Duration `json:",omitempty"`
}
// Metric is an object that describes a metric, with its name, the creator and
// owner program name, its Kind, a sequence of Keys that may be used to
// add dimension to the metric, and a list of LabelValues that contain data for
// labels in each dimension of the Keys.
type Metric struct {
sync.RWMutex
Name string // Name
Program string // Instantiating program
Kind Kind
Type Type
Hidden bool `json:",omitempty"`
Keys []string `json:",omitempty"`
LabelValues []*LabelValue `json:",omitempty"`
labelValuesMap map[string]*LabelValue
Source string `json:",omitempty"`
Buckets []datum.Range `json:",omitempty"`
}
// NewMetric returns a new empty metric of dimension len(keys).
func NewMetric(name string, prog string, kind Kind, typ Type, keys ...string) *Metric {
m := newMetric(len(keys))
m.Name = name
m.Program = prog
m.Kind = kind
m.Type = typ
copy(m.Keys, keys)
return m
}
// newMetric returns a new empty Metric.
func newMetric(keyLen int) *Metric {
return &Metric{
Keys: make([]string, keyLen),
LabelValues: make([]*LabelValue, 0),
labelValuesMap: make(map[string]*LabelValue),
}
}
// buildLabelValueKey returns a unique key for the given labels.
func buildLabelValueKey(labels []string) string {
var buf strings.Builder
for i := 0; i < len(labels); i++ {
rs := strings.ReplaceAll(labels[i], "-", "\\-")
buf.WriteString(rs)
buf.WriteString("-")
}
return buf.String()
}
func (m *Metric) AppendLabelValue(lv *LabelValue) error {
if len(lv.Labels) != len(m.Keys) {
return errors.Errorf("Label values requested (%q) not same length as keys for metric %v", lv.Labels, m)
}
m.LabelValues = append(m.LabelValues, lv)
k := buildLabelValueKey(lv.Labels)
m.labelValuesMap[k] = lv
return nil
}
func (m *Metric) FindLabelValueOrNil(labelvalues []string) *LabelValue {
k := buildLabelValueKey(labelvalues)
lv, ok := m.labelValuesMap[k]
if ok {
return lv
}
return nil
}
// GetDatum returns the datum named by a sequence of string label values from a
// Metric. If the sequence of label values does not yet exist, it is created.
func (m *Metric) GetDatum(labelvalues ...string) (d datum.Datum, err error) {
if len(labelvalues) != len(m.Keys) {
return nil, errors.Errorf("Label values requested (%q) not same length as keys for metric %v", labelvalues, m)
}
m.Lock()
defer m.Unlock()
if lv := m.FindLabelValueOrNil(labelvalues); lv != nil {
d = lv.Value
} else {
switch m.Type {
case Int:
d = datum.NewInt()
case Float:
d = datum.NewFloat()
case String:
d = datum.NewString()
case Buckets:
buckets := m.Buckets
if buckets == nil {
buckets = make([]datum.Range, 0)
}
d = datum.NewBuckets(buckets)
}
lv := &LabelValue{Labels: labelvalues, Value: d}
if err := m.AppendLabelValue(lv); err != nil {
return nil, err
}
}
return d, nil
}
// RemoveDatum removes the Datum described by labelvalues from the Metric m.
func (m *Metric) RemoveDatum(labelvalues ...string) error {
if len(labelvalues) != len(m.Keys) {
return errors.Errorf("Label values requested (%q) not same length as keys for metric %v", labelvalues, m)
}
m.Lock()
defer m.Unlock()
k := buildLabelValueKey(labelvalues)
olv, ok := m.labelValuesMap[k]
if ok {
for i, lv := range m.LabelValues {
if lv == olv {
// remove from the slice
m.LabelValues = append(m.LabelValues[:i], m.LabelValues[i+1:]...)
delete(m.labelValuesMap, k)
break
}
}
}
return nil
}
func (m *Metric) ExpireDatum(expiry time.Duration, labelvalues ...string) error {
if len(labelvalues) != len(m.Keys) {
return errors.Errorf("Label values requested (%q) not same length as keys for metric %v", labelvalues, m)
}
m.Lock()
defer m.Unlock()
if lv := m.FindLabelValueOrNil(labelvalues); lv != nil {
lv.Expiry = expiry
return nil
}
return errors.Errorf("No datum for given labelvalues %q", labelvalues)
}
// LabelSet is an object that maps the keys of a Metric to the labels naming a
// Datum, for use when enumerating Datums from a Metric.
type LabelSet struct {
Labels map[string]string
Datum datum.Datum
}
func zip(keys []string, values []string) map[string]string {
r := make(map[string]string)
for i, v := range values {
r[keys[i]] = v
}
return r
}
// EmitLabelSets enumerates the LabelSets corresponding to the LabelValues of a
// Metric. It emits them onto the provided channel, then closes the channel to
// signal completion.
func (m *Metric) EmitLabelSets(c chan *LabelSet) {
for _, lv := range m.LabelValues {
ls := &LabelSet{zip(m.Keys, lv.Labels), lv.Value}
c <- ls
}
close(c)
}
// UnmarshalJSON converts a JSON byte string into a LabelValue.
func (lv *LabelValue) UnmarshalJSON(b []byte) error {
var obj map[string]*json.RawMessage
err := json.Unmarshal(b, &obj)
if err != nil {
return err
}
labels := make([]string, 0)
if _, ok := obj["Labels"]; ok {
err = json.Unmarshal(*obj["Labels"], &labels)
if err != nil {
return err
}
}
lv.Labels = labels
var valObj map[string]*json.RawMessage
err = json.Unmarshal(*obj["Value"], &valObj)
if err != nil {
return err
}
var t int64
err = json.Unmarshal(*valObj["Time"], &t)
if err != nil {
return err
}
var i int64
err = json.Unmarshal(*valObj["Value"], &i)
if err != nil {
return err
}
lv.Value = datum.MakeInt(i, time.Unix(t/1e9, t%1e9))
return nil
}
func (m *Metric) String() string {
m.RLock()
defer m.RUnlock()
return fmt.Sprintf("Metric: name=%s program=%s kind=%v type=%s hidden=%v keys=%v labelvalues=%v source=%s buckets=%v", m.Name, m.Program, m.Kind, m.Type, m.Hidden, m.Keys, m.LabelValues, m.Source, m.Buckets)
}
// SetSource sets the source of a metric, describing where in user programmes it was defined.
func (m *Metric) SetSource(source string) {
m.Lock()
defer m.Unlock()
m.Source = source
}
mtail-3.0.0~rc48/internal/metrics/metric_test.go 0000664 0000000 0000000 00000014204 14121212652 0021647 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
"sync"
"testing"
"testing/quick"
"time"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
func TestKindType(t *testing.T) {
v := Kind(0)
if s := v.String(); s != "Unknown" {
t.Errorf("Kind.String() returned %q not Unknown", s)
}
v = Counter
if s := v.String(); s != "Counter" {
t.Errorf("Kind.String() returned %q not Counter", s)
}
v = Gauge
if s := v.String(); s != "Gauge" {
t.Errorf("Kind.String() returned %q not Gauge", s)
}
v = Timer
if s := v.String(); s != "Timer" {
t.Errorf("Kind.String() returned %q not Timer", s)
}
}
func TestScalarMetric(t *testing.T) {
v := NewMetric("test", "prog", Counter, Int)
d, err := v.GetDatum()
if err != nil {
t.Errorf("no datum: %s", err)
}
datum.IncIntBy(d, 1, time.Now().UTC())
lv := v.FindLabelValueOrNil([]string{})
if lv == nil {
t.Fatal("couldn't find labelvalue")
}
newD := lv.Value
if newD == nil {
t.Error("new_d is nil")
}
if newD.ValueString() != "1" {
t.Error("value not 1")
}
d2, err := v.GetDatum("a", "b")
if err == nil {
t.Errorf("datum with keys sohuld have returned no value, got %v", d2)
}
}
func TestDimensionedMetric(t *testing.T) {
v := NewMetric("test", "prog", Counter, Int, "foo")
d, _ := v.GetDatum("a")
datum.IncIntBy(d, 1, time.Now().UTC())
if v.FindLabelValueOrNil([]string{"a"}).Value.ValueString() != "1" {
t.Errorf("fail")
}
v = NewMetric("test", "prog", Counter, Int, "foo", "bar")
d, _ = v.GetDatum("a", "b")
datum.IncIntBy(d, 1, time.Now().UTC())
if v.FindLabelValueOrNil([]string{"a", "b"}).Value.ValueString() != "1" {
t.Errorf("fail")
}
v = NewMetric("test", "prog", Counter, Int, "foo", "bar", "quux")
d, _ = v.GetDatum("a", "b", "c")
datum.IncIntBy(d, 1, time.Now().UTC())
if v.FindLabelValueOrNil([]string{"a", "b", "c"}).Value.ValueString() != "1" {
t.Errorf("fail")
}
}
var labelSetTests = []struct {
values []string
expectedLabels map[string]string
}{
{
[]string{"a", "b", "c"},
map[string]string{"foo": "a", "bar": "b", "quux": "c"},
},
{
[]string{"a", "b", "d"},
map[string]string{"foo": "a", "bar": "b", "quux": "d"},
},
}
func TestEmitLabelSet(t *testing.T) {
ts := time.Now().UTC()
for _, tc := range labelSetTests {
tc := tc
t.Run(fmt.Sprintf("%v", tc.values), func(t *testing.T) {
m := NewMetric("test", "prog", Gauge, Int, "foo", "bar", "quux")
d, _ := m.GetDatum(tc.values...)
datum.SetInt(d, 37, ts)
c := make(chan *LabelSet)
go m.EmitLabelSets(c)
ls := <-c
testutil.ExpectNoDiff(t, tc.expectedLabels, ls.Labels)
})
}
}
func TestFindLabelValueOrNil(t *testing.T) {
m0 := NewMetric("foo", "prog", Counter, Int)
if r0 := m0.FindLabelValueOrNil([]string{}); r0 != nil {
t.Errorf("m0 should be nil: %v", r0)
}
d, err := m0.GetDatum()
if err != nil {
t.Errorf("Bad datum %v: %v\n", d, err)
}
if r1 := m0.FindLabelValueOrNil([]string{}); r1 == nil {
t.Errorf("m0 should not be nil: %v", r1)
}
m1 := NewMetric("bar", "prog", Counter, Int, "a")
d1, err1 := m1.GetDatum("1")
if err1 != nil {
t.Errorf("err1 %v: %v\n", d1, err1)
}
if r2 := m1.FindLabelValueOrNil([]string{"0"}); r2 != nil {
t.Errorf("r2 should be nil")
}
if r3 := m1.FindLabelValueOrNil([]string{"1"}); r3 == nil {
t.Errorf("r3 should be non nil")
}
}
func TestAppendLabelValue(t *testing.T) {
m := NewMetric("foo", "prog", Counter, Int, "bar")
l := []string{"test"}
d0 := datum.MakeInt(66, time.Unix(0, 0))
lv := &LabelValue{Labels: l, Value: d0}
err := m.AppendLabelValue(lv)
if err != nil {
t.Errorf("Bad append %v: %v\n", d0, err)
}
d1, err := m.GetDatum(l...)
if err != nil {
t.Errorf("Bad datum %v: %v\n", d1, err)
}
testutil.ExpectNoDiff(t, d0, d1)
}
func timeGenerator(rand *rand.Rand) time.Time {
months := []time.Month{
time.January, time.February, time.March,
time.April, time.May, time.June,
time.July, time.August, time.September,
time.October, time.November, time.December,
}
return time.Date(
rand.Intn(9999),
months[rand.Intn(len(months))],
rand.Intn(31),
rand.Intn(24),
rand.Intn(60),
rand.Intn(60),
int(rand.Int31()),
time.UTC,
)
}
func TestMetricJSONRoundTrip(t *testing.T) {
rand := rand.New(rand.NewSource(0))
f := func(name, prog string, kind Kind, keys []string, val, ti, tns int64) bool {
m := NewMetric(name, prog, kind, Int, keys...)
labels := make([]string, 0)
for range keys {
if l, ok := quick.Value(reflect.TypeOf(name), rand); ok {
labels = append(labels, l.String())
} else {
t.Errorf("failed to create value for labels")
break
}
}
d, _ := m.GetDatum(labels...)
datum.SetInt(d, val, timeGenerator(rand))
j, e := json.Marshal(m)
if e != nil {
t.Errorf("json.Marshal failed: %s\n", e)
return false
}
r := newMetric(0)
e = json.Unmarshal(j, &r)
if e != nil {
t.Errorf("json.Unmarshal failed: %s\n", e)
return false
}
return testutil.ExpectNoDiff(t, m, r, testutil.IgnoreUnexported(sync.RWMutex{}, Metric{}))
}
if err := quick.Check(f, nil); err != nil {
t.Error(err)
}
}
func TestTimer(t *testing.T) {
m := NewMetric("test", "prog", Timer, Int)
n := NewMetric("test", "prog", Timer, Int)
testutil.ExpectNoDiff(t, m, n, testutil.IgnoreUnexported(sync.RWMutex{}, Metric{}))
d, _ := m.GetDatum()
datum.IncIntBy(d, 1, time.Now().UTC())
lv := m.FindLabelValueOrNil([]string{})
if lv == nil {
t.Fatal("couldn't find labelvalue")
}
newD := lv.Value
if newD == nil {
t.Errorf("new_d is nil")
}
if newD.ValueString() != "1" {
t.Errorf("value not 1")
}
}
func TestRemoveMetricLabelValue(t *testing.T) {
m := NewMetric("test", "prog", Counter, Int, "a", "b", "c")
_, e := m.GetDatum("a", "a", "a")
if e != nil {
t.Errorf("Getdatum failed: %s", e)
}
lv := m.FindLabelValueOrNil([]string{"a", "a", "a"})
if lv == nil {
t.Errorf("coidln't find labelvalue")
}
e = m.RemoveDatum("a", "a", "a")
if e != nil {
t.Errorf("couldn't remove datum: %s", e)
}
lv = m.FindLabelValueOrNil([]string{"a", "a", "a"})
if lv != nil {
t.Errorf("label value still exists")
}
}
mtail-3.0.0~rc48/internal/metrics/store.go 0000664 0000000 0000000 00000012264 14121212652 0020465 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
import (
"context"
"encoding/json"
"io"
"reflect"
"sync"
"time"
"github.com/golang/glog"
"github.com/pkg/errors"
)
// Store contains Metrics.
type Store struct {
searchMu sync.RWMutex // read for iterate and insert, write for delete
insertMu sync.Mutex // locked for insert and delete, unlocked for iterate
Metrics map[string][]*Metric
}
// NewStore returns a new metric Store.
func NewStore() (s *Store) {
s = &Store{}
s.ClearMetrics()
return
}
// Add is used to add one metric to the Store.
func (s *Store) Add(m *Metric) error {
s.insertMu.Lock()
defer s.insertMu.Unlock()
s.searchMu.RLock()
glog.V(1).Infof("Adding a new metric %v", m)
dupeIndex := -1
if len(s.Metrics[m.Name]) > 0 {
t := s.Metrics[m.Name][0].Kind
if m.Kind != t {
s.searchMu.RUnlock()
return errors.Errorf("Metric %s has different kind %v to existing %v.", m.Name, m.Kind, t)
}
// To avoid duplicate metrics:
// - copy old LabelValues into new metric;
// - discard old metric.
for i, v := range s.Metrics[m.Name] {
if v.Program != m.Program {
continue
}
if v.Type != m.Type {
continue
}
if v.Source != m.Source {
continue
}
dupeIndex = i
glog.V(2).Infof("v keys: %v m.keys: %v", v.Keys, m.Keys)
// If a set of label keys has changed, discard
// old metric completely, w/o even copying old
// data, as they are now incompatible.
if len(v.Keys) != len(m.Keys) || !reflect.DeepEqual(v.Keys, m.Keys) {
break
}
glog.V(2).Infof("v buckets: %v m.buckets: %v", v.Buckets, m.Buckets)
// Otherwise, copy everything into the new metric
glog.V(2).Infof("Found duped metric: %d", dupeIndex)
for j, oldLabel := range v.LabelValues {
glog.V(2).Infof("Labels: %d %s", j, oldLabel.Labels)
d, err := v.GetDatum(oldLabel.Labels...)
if err != nil {
return err
}
if err = m.RemoveDatum(oldLabel.Labels...); err != nil {
return err
}
lv := &LabelValue{Labels: oldLabel.Labels, Value: d}
if err := m.AppendLabelValue(lv); err != nil {
return err
}
}
}
}
s.searchMu.RUnlock()
// We're in modify mode now so lock out search
s.searchMu.Lock()
s.Metrics[m.Name] = append(s.Metrics[m.Name], m)
if dupeIndex >= 0 {
glog.V(2).Infof("removing original, keeping its clone")
s.Metrics[m.Name] = append(s.Metrics[m.Name][0:dupeIndex], s.Metrics[m.Name][dupeIndex+1:]...)
}
s.searchMu.Unlock()
return nil
}
// FindMetricOrNil returns a metric in a store, or returns nil if not found.
func (s *Store) FindMetricOrNil(name, prog string) *Metric {
s.searchMu.RLock()
defer s.searchMu.RUnlock()
ml, ok := s.Metrics[name]
if !ok {
return nil
}
for _, m := range ml {
if m.Program != prog {
continue
}
return m
}
return nil
}
// ClearMetrics empties the store of all metrics.
func (s *Store) ClearMetrics() {
s.insertMu.Lock()
defer s.insertMu.Unlock()
s.searchMu.Lock()
defer s.searchMu.Unlock()
s.Metrics = make(map[string][]*Metric)
}
// MarshalJSON returns a JSON byte string representing the Store.
func (s *Store) MarshalJSON() (b []byte, err error) {
s.searchMu.RLock()
defer s.searchMu.RUnlock()
ms := make([]*Metric, 0)
for _, ml := range s.Metrics {
ms = append(ms, ml...)
}
return json.Marshal(ms)
}
// Range calls f sequentially for each Metric present in the store.
// The Metric is not locked when f is called.
// If f returns non nil error, Range stops the iteration.
// This looks a lot like sync.Map, ay.
func (s *Store) Range(f func(*Metric) error) error {
s.searchMu.RLock()
defer s.searchMu.RUnlock()
for _, ml := range s.Metrics {
for _, m := range ml {
if err := f(m); err != nil {
return err
}
}
}
return nil
}
// Gc iterates through the Store looking for metrics that have been marked
// for expiry, and removing them if their expiration time has passed.
func (s *Store) Gc() error {
glog.Info("Running Store.Expire()")
now := time.Now()
return s.Range(func(m *Metric) error {
for _, lv := range m.LabelValues {
if lv.Expiry <= 0 {
continue
}
if now.Sub(lv.Value.TimeUTC()) > lv.Expiry {
err := m.RemoveDatum(lv.Labels...)
if err != nil {
return err
}
}
}
return nil
})
}
// StartGcLoop runs a permanent goroutine to expire metrics every duration.
func (s *Store) StartGcLoop(ctx context.Context, duration time.Duration) {
if duration <= 0 {
glog.Infof("Metric store expiration disabled")
return
}
go func() {
glog.Infof("Starting metric store expiry loop every %s", duration.String())
ticker := time.NewTicker(duration)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := s.Gc(); err != nil {
glog.Info(err)
}
case <-ctx.Done():
return
}
}
}()
}
// WriteMetrics dumps the current state of the metrics store in JSON format to
// the io.Writer.
func (s *Store) WriteMetrics(w io.Writer) error {
s.searchMu.RLock()
b, err := json.MarshalIndent(s.Metrics, "", " ")
s.searchMu.RUnlock()
if err != nil {
return errors.Wrap(err, "failed to marshal metrics into json")
}
_, err = w.Write(b)
if err != nil {
return errors.Wrap(err, "failed to write metrics")
}
return nil
}
mtail-3.0.0~rc48/internal/metrics/store_bench_test.go 0000664 0000000 0000000 00000011512 14121212652 0022656 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
import (
"fmt"
"math"
"math/rand"
"reflect"
"testing"
"testing/quick"
)
const (
maxItemsLog2 = 10
maxLabelsLog2 = 13
)
// newRandMetric makes a new, randomly filled Metric.
func newRandMetric(tb testing.TB, rand *rand.Rand, i int) *Metric {
tb.Helper()
nameVal, ok := quick.Value(reflect.TypeOf(""), rand)
if !ok {
tb.Fatalf("%d: can't make a name", i)
}
progVal, ok := quick.Value(reflect.TypeOf(""), rand)
if !ok {
tb.Fatalf("%d: can't make a prog", i)
}
kindVal, ok := quick.Value(reflect.TypeOf(Counter), rand)
if !ok {
tb.Fatalf("%d: can't make a kind", i)
}
typeVal, ok := quick.Value(reflect.TypeOf(Int), rand)
if !ok {
tb.Fatalf("%d: can't make a type", i)
}
keysVal, ok := quick.Value(reflect.TypeOf([]string{}), rand)
if !ok {
tb.Fatalf("%d: can't make a key list", i)
}
return NewMetric(nameVal.Interface().(string),
progVal.Interface().(string),
kindVal.Interface().(Kind),
typeVal.Interface().(Type),
keysVal.Interface().([]string)...)
}
type bench struct {
name string
setup func(b *testing.B, rand *rand.Rand, items int, m *[]*Metric, s *Store)
b func(b *testing.B, items int, m []*Metric, s *Store)
}
func fillMetric(b *testing.B, rand *rand.Rand, items int, m *[]*Metric, _ *Store) {
b.Helper()
for i := 0; i < items; i++ {
(*m)[i] = newRandMetric(b, rand, i)
}
}
func addToStore(b *testing.B, items int, m []*Metric, s *Store) {
b.Helper()
for j := 0; j < items; j++ {
s.Add(m[j])
}
}
func BenchmarkStore(b *testing.B) {
benches := []bench{
{
name: "Add",
setup: fillMetric,
b: addToStore,
},
{
name: "Iterate",
setup: func(b *testing.B, rand *rand.Rand, items int, m *[]*Metric, s *Store) {
b.Helper()
fillMetric(b, rand, items, m, s)
addToStore(b, items, *m, s)
},
b: func(b *testing.B, items int, m []*Metric, s *Store) {
b.Helper()
s.Range(func(*Metric) error {
return nil
})
},
},
}
rand := rand.New(rand.NewSource(99))
for _, bench := range benches {
bench := bench
for _, gc := range []bool{false, true} {
gc := gc
gcStr := ""
if gc {
gcStr = "WithGc"
}
for _, parallel := range []bool{false, true} {
parallel := parallel
parallelStr := ""
if parallel {
parallelStr = "Parallel"
}
for i := 0.; i <= maxItemsLog2; i++ {
items := int(math.Pow(2, i))
b.Run(fmt.Sprintf("%s%s%s-%d", bench.name, gcStr, parallelStr, items), func(b *testing.B) {
s := NewStore()
m := make([]*Metric, items)
if bench.setup != nil {
bench.setup(b, rand, items, &m, s)
}
b.ResetTimer()
if parallel {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
bench.b(b, items, m, s)
}
})
} else {
for n := 0; n < b.N; n++ {
bench.b(b, items, m, s)
if gc {
s.Gc()
}
}
}
})
}
}
}
}
}
func newRandLabels(tb testing.TB, rand *rand.Rand, i int) []string {
tb.Helper()
lv := make([]string, i)
for j := 0; j < i; j++ {
val, ok := quick.Value(reflect.TypeOf(""), rand)
if !ok {
tb.Fatalf("%d-%d: can't make a label", i, j)
}
lv[j] = val.Interface().(string)
}
return lv
}
func fillLabel(b *testing.B, rand *rand.Rand, items, keys int, lvs *[][]string, _ *Metric) {
b.Helper()
for i := 0; i < items; i++ {
(*lvs)[i] = newRandLabels(b, rand, keys)
}
}
func getDatum(b *testing.B, items int, lvs *[][]string, m *Metric) {
b.Helper()
for j := 0; j < items; j++ {
lv := (*lvs)[j]
m.GetDatum(lv...)
}
}
type metricBench struct {
name string
setup func(b *testing.B, rand *rand.Rand, items, keys int, lvs *[][]string, m *Metric)
b func(b *testing.B, items int, lv *[][]string, m *Metric)
}
func BenchmarkMetric(b *testing.B) {
maxKeys := 4
benches := []metricBench{
{
name: "GetDatum",
setup: fillLabel,
b: getDatum,
},
}
rand := rand.New(rand.NewSource(99))
for _, bench := range benches {
bench := bench
for _, parallel := range []bool{false, true} {
parallel := parallel
parallelStr := ""
if parallel {
parallelStr = "Parallel"
}
for i := 1; i <= maxLabelsLog2; i++ {
items := int(math.Pow(2, float64(i)))
lv := newRandLabels(b, rand, maxKeys)
b.Run(fmt.Sprintf("%s%s-%d", bench.name, parallelStr, items), func(b *testing.B) {
m := NewMetric("test", "prog", Counter, Int, lv...)
lvs := make([][]string, items)
if bench.setup != nil {
bench.setup(b, rand, items, maxKeys, &lvs, m)
}
b.ResetTimer()
if parallel {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
bench.b(b, items, &lvs, m)
}
})
} else {
for n := 0; n < b.N; n++ {
bench.b(b, items, &lvs, m)
}
}
})
}
}
}
}
mtail-3.0.0~rc48/internal/metrics/store_test.go 0000664 0000000 0000000 00000006745 14121212652 0021533 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
import (
"testing"
"time"
"github.com/golang/glog"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
func TestMatchingKind(t *testing.T) {
s := NewStore()
m1 := NewMetric("foo", "prog", Counter, Int)
err := s.Add(m1)
testutil.FatalIfErr(t, err)
m2 := NewMetric("foo", "prog1", Gauge, Int)
err = s.Add(m2)
if err == nil {
t.Fatal("should be err")
}
}
func TestDuplicateMetric(t *testing.T) {
expectedMetrics := 0
s := NewStore()
_ = s.Add(NewMetric("foo", "prog", Counter, Int, "user", "host"))
_ = s.Add(NewMetric("foo", "prog", Counter, Int))
expectedMetrics++
if len(s.Metrics["foo"]) != expectedMetrics {
t.Fatalf("should not add duplicate metric. Store: %v", s)
}
_ = s.Add(NewMetric("foo", "prog", Counter, Float))
glog.Infof("Store: %v", s)
expectedMetrics++
if len(s.Metrics["foo"]) != expectedMetrics {
t.Fatalf("should add metric of a different type: %v", s)
}
_ = s.Add(NewMetric("foo", "prog", Counter, Int, "user", "host", "zone", "domain"))
glog.Infof("Store: %v", s)
if len(s.Metrics["foo"]) != expectedMetrics {
t.Fatalf("should not add duplicate metric, but replace the old one. Store: %v", s)
}
_ = s.Add(NewMetric("foo", "prog1", Counter, Int))
glog.Infof("Store: %v", s)
expectedMetrics++
if len(s.Metrics["foo"]) != expectedMetrics {
t.Fatalf("should add metric with a different prog: %v", s)
}
_ = s.Add(NewMetric("foo", "prog1", Counter, Float))
glog.Infof("Store: %v", s)
expectedMetrics++
if len(s.Metrics["foo"]) != expectedMetrics {
t.Fatalf("should add metric of a different type: %v", s)
}
}
/* A program can add a metric with the same name and
of different type.
Prometheus behavior in this case is undefined.
@see https://github.com/google/mtail/issues/130
*/
func TestAddMetricDifferentType(t *testing.T) {
expected := 2
s := NewStore()
err := s.Add(NewMetric("foo", "prog", Counter, Int))
testutil.FatalIfErr(t, err)
// Duplicate metric of different type from *the same program
err = s.Add(NewMetric("foo", "prog", Counter, Float))
testutil.FatalIfErr(t, err)
if len(s.Metrics["foo"]) != expected {
t.Fatalf("should have %d metrics of different Type: %v", expected, s.Metrics)
}
// Duplicate metric of different type from a different program
err = s.Add(NewMetric("foo", "prog1", Counter, Float))
expected++
testutil.FatalIfErr(t, err)
if len(s.Metrics["foo"]) != expected {
t.Fatalf("should have %d metrics of different Type: %v", expected, s.Metrics)
}
}
func TestExpireMetric(t *testing.T) {
s := NewStore()
m := NewMetric("foo", "prog", Counter, Int, "a", "b", "c")
testutil.FatalIfErr(t, s.Add(m))
d, err := m.GetDatum("1", "2", "3")
if err != nil {
t.Error(err)
}
datum.SetInt(d, 1, time.Now().Add(-time.Hour))
lv := m.FindLabelValueOrNil([]string{"1", "2", "3"})
if lv == nil {
t.Fatal("couldn't find lv")
}
lv.Expiry = time.Minute
d, err = m.GetDatum("4", "5", "6")
if err != nil {
t.Error(err)
}
datum.SetInt(d, 1, time.Now().Add(-time.Hour))
lv = m.FindLabelValueOrNil([]string{"4", "5", "6"})
if lv == nil {
t.Errorf("couldn't find lv")
}
testutil.FatalIfErr(t, s.Gc())
lv = m.FindLabelValueOrNil([]string{"1", "2", "3"})
if lv != nil {
t.Errorf("lv not expired: %#v", lv)
t.Logf("Store: %#v", s)
}
lv = m.FindLabelValueOrNil([]string{"4", "5", "6"})
if lv == nil {
t.Errorf("lv expired")
t.Logf("Store: %#v", s)
}
}
mtail-3.0.0~rc48/internal/metrics/testing.go 0000664 0000000 0000000 00000002525 14121212652 0021005 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
type MetricSlice []*Metric
func (s MetricSlice) Len() int { return len(s) }
func (s MetricSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s MetricSlice) Less(i, j int) bool {
return Less(s[i], s[j])
}
func Less(m1, m2 *Metric) bool {
if m1.Name < m2.Name {
return true
}
if m1.Name > m2.Name {
return false
}
if m1.Program < m2.Program {
return true
}
if m1.Program > m2.Program {
return false
}
if m1.Kind < m2.Kind {
return true
}
if m1.Kind > m2.Kind {
return false
}
if m1.Type < m2.Type {
return true
}
if m1.Type > m2.Type {
return false
}
if len(m1.Keys) < len(m2.Keys) {
return true
}
if len(m1.Keys) > len(m2.Keys) {
return false
}
for x, k := range m1.Keys {
if k < m2.Keys[x] {
return true
}
if k > m2.Keys[x] {
return false
}
}
for x, lv := range m1.LabelValues {
if len(lv.Labels) < len(m2.LabelValues[x].Labels) {
return true
}
if len(lv.Labels) > len(m2.LabelValues[x].Labels) {
return false
}
for y, k := range lv.Labels {
if k < m2.LabelValues[x].Labels[y] {
return true
}
if k > m2.LabelValues[x].Labels[y] {
return false
}
}
// if lv.Value < m2.LabelValues[x].Value {
// return true
// }
}
return false
}
mtail-3.0.0~rc48/internal/metrics/type.go 0000664 0000000 0000000 00000001656 14121212652 0020315 0 ustar 00root root 0000000 0000000 // Copyright 2017 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package metrics
import (
"math/rand"
"reflect"
)
// Type describes the type of value stored in a Datum.
type Type int
const (
// Int indicates this metric is an integer metric type.
Int Type = iota
// Float indicates this metric is a floating-point metric type.
Float
// String indicates this metric contains printable string values.
String
// Buckets indicates this metric is a histogram metric type.
Buckets
endType // end of enumeration for testing
)
func (t Type) String() string {
switch t {
case Int:
return "Int"
case Float:
return "Float"
case String:
return "String"
case Buckets:
return "Buckets"
}
return "?"
}
// Generate implements the quick.Generator interface for Type.
func (Type) Generate(rand *rand.Rand, size int) reflect.Value {
return reflect.ValueOf(Type(rand.Intn(int(endType))))
}
mtail-3.0.0~rc48/internal/mtail/ 0000775 0000000 0000000 00000000000 14121212652 0016435 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/mtail/basic_tail_integration_test.go 0000664 0000000 0000000 00000003315 14121212652 0024522 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"fmt"
"os"
"path/filepath"
"sync"
"testing"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestBasicTail(t *testing.T) {
testutil.SkipIfShort(t)
if testing.Verbose() {
testutil.SetFlag(t, "vmodule", "tail=2,log_watcher=2")
}
logDir := testutil.TestTempDir(t)
m, stopM := mtail.TestStartServer(t, 1, mtail.LogPathPatterns(logDir+"/*"), mtail.ProgramPath("../../examples/linecount.mtail"))
defer stopM()
logFile := filepath.Join(logDir, "log")
lineCountCheck := m.ExpectMapExpvarDeltaWithDeadline("log_lines_total", logFile, 3)
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 1)
f := testutil.TestOpenFile(t, logFile)
m.PollWatched(1) // Force sync to EOF
for i := 1; i <= 3; i++ {
testutil.WriteString(t, f, fmt.Sprintf("%d\n", i))
}
m.PollWatched(1) // Expect to read 3 lines here.
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
lineCountCheck()
}()
go func() {
defer wg.Done()
logCountCheck()
}()
wg.Wait()
}
func TestNewLogDoesNotMatchIsIgnored(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
// Start mtail
logFilepath := filepath.Join(workdir, "log")
m, stopM := mtail.TestStartServer(t, 0, mtail.LogPathPatterns(logFilepath))
defer stopM()
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 0)
// touch log file
newLogFilepath := filepath.Join(workdir, "log1")
logFile, err := os.Create(newLogFilepath)
testutil.FatalIfErr(t, err)
defer logFile.Close()
m.PollWatched(0) // No streams so don't wait for any.
logCountCheck()
}
mtail-3.0.0~rc48/internal/mtail/buildinfo.go 0000664 0000000 0000000 00000001030 14121212652 0020731 0 ustar 00root root 0000000 0000000 // Copyright 2020 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail
import (
"fmt"
"runtime"
)
// BuildInfo records the compile-time information for use when reporting the mtail version.
type BuildInfo struct {
Branch string
Version string
Revision string
}
func (b BuildInfo) String() string {
return fmt.Sprintf(
"mtail version %s git revision %s go version %s go arch %s go os %s",
b.Version,
b.Revision,
runtime.Version(),
runtime.GOARCH,
runtime.GOOS,
)
}
mtail-3.0.0~rc48/internal/mtail/compile_only_integration_test.go 0000664 0000000 0000000 00000001642 14121212652 0025122 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"context"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestBadProgramFailsCompilation(t *testing.T) {
testutil.SkipIfShort(t)
progDir := testutil.TestTempDir(t)
err := ioutil.WriteFile(filepath.Join(progDir, "bad.mtail"), []byte("asdfasdf\n"), 0666)
testutil.FatalIfErr(t, err)
ctx := context.Background()
// Compile-only fails program compilation at server start, not after it's running.
_, err = mtail.New(ctx, metrics.NewStore(), mtail.ProgramPath(progDir), mtail.CompileOnly)
if err == nil {
t.Error("expected error from mtail")
}
if !strings.Contains(err.Error(), "compile failed") {
t.Error("compile failed not reported")
}
}
mtail-3.0.0~rc48/internal/mtail/examples_integration_test.go 0000664 0000000 0000000 00000025111 14121212652 0024244 0 ustar 00root root 0000000 0000000 // Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"context"
"fmt"
"io"
"net"
"os"
"path/filepath"
"sync"
"testing"
"time"
"github.com/golang/glog"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/mtail/golden"
"github.com/google/mtail/internal/testutil"
"github.com/google/mtail/internal/waker"
"golang.org/x/sys/unix"
)
const exampleTimeout = 2 * time.Second
var exampleProgramTests = []struct {
programfile string // Example program file.
logfile string // Sample log input.
goldenfile string // Expected metrics after processing.
}{
{
"examples/rsyncd.mtail",
"testdata/rsyncd.log",
"testdata/rsyncd.golden",
},
{
"examples/sftp.mtail",
"testdata/sftp_chroot.log",
"testdata/sftp_chroot.golden",
},
{
"examples/dhcpd.mtail",
"testdata/anonymised_dhcpd_log",
"testdata/anonymised_dhcpd_log.golden",
},
{
"examples/ntpd.mtail",
"testdata/ntp4",
"testdata/ntp4.golden",
},
{
"examples/ntpd_peerstats.mtail",
"testdata/xntp3_peerstats",
"testdata/xntp3_peerstats.golden",
},
{
"examples/apache_combined.mtail",
"testdata/apache-combined.log",
"testdata/apache-combined.golden",
},
{
"examples/apache_common.mtail",
"testdata/apache-common.log",
"testdata/apache-common.golden",
},
{
"examples/vsftpd.mtail",
"testdata/vsftpd_log",
"testdata/vsftpd_log.golden",
},
{
"examples/vsftpd.mtail",
"testdata/vsftpd_xferlog",
"testdata/vsftpd_xferlog.golden",
},
{
"examples/lighttpd.mtail",
"testdata/lighttpd_access.log",
"testdata/lighttpd_accesslog.golden",
},
{
"examples/mysql_slowqueries.mtail",
"testdata/mysql_slowqueries.log",
"testdata/mysql_slowqueries.golden",
},
}
func TestExamplePrograms(t *testing.T) {
testutil.SkipIfShort(t)
for _, tc := range exampleProgramTests {
tc := tc
t.Run(fmt.Sprintf("%s on %s", tc.programfile, tc.logfile),
testutil.TimeoutTest(exampleTimeout, func(t *testing.T) { //nolint:thelper
ctx, cancel := context.WithCancel(context.Background())
waker, _ := waker.NewTest(ctx, 0) // oneshot means we should never need to wake the stream
store := metrics.NewStore()
programFile := filepath.Join("../..", tc.programfile)
mtail, err := mtail.New(ctx, store, mtail.ProgramPath(programFile), mtail.LogPathPatterns(tc.logfile), mtail.OneShot, mtail.OmitMetricSource, mtail.DumpAstTypes, mtail.DumpBytecode, mtail.LogPatternPollWaker(waker), mtail.LogstreamPollWaker(waker))
testutil.FatalIfErr(t, err)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
testutil.FatalIfErr(t, mtail.Run())
}()
// Oneshot mode means we can wait for shutdown before cancelling.
wg.Wait()
cancel()
g, err := os.Open(tc.goldenfile)
testutil.FatalIfErr(t, err)
defer g.Close()
goldenStore := golden.ReadTestData(g, tc.programfile)
var storeList metrics.MetricSlice
store.Range(func(m *metrics.Metric) error {
storeList = append(storeList, m)
return nil
})
testutil.ExpectNoDiff(t, goldenStore, storeList, testutil.SortSlices(metrics.Less), testutil.IgnoreUnexported(metrics.Metric{}, sync.RWMutex{}, datum.String{}))
}))
}
}
// This test only compiles examples, but has coverage over all examples
// provided. This ensures we ship at least syntactically correct examples.
func TestCompileExamplePrograms(t *testing.T) {
testutil.SkipIfShort(t)
matches, err := filepath.Glob("../../examples/*.mtail")
testutil.FatalIfErr(t, err)
for _, tc := range matches {
tc := tc
name := filepath.Base(tc)
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
s := metrics.NewStore()
mtail, err := mtail.New(ctx, s, mtail.ProgramPath(tc), mtail.CompileOnly, mtail.OmitMetricSource, mtail.DumpAstTypes, mtail.DumpBytecode)
testutil.FatalIfErr(t, err)
// Ensure that run shuts down for CompileOnly
testutil.FatalIfErr(t, mtail.Run())
cancel()
})
}
}
func BenchmarkProgram(b *testing.B) {
for _, bm := range exampleProgramTests {
bm := bm
b.Run(fmt.Sprintf("%s on %s", bm.programfile, bm.logfile), func(b *testing.B) {
b.ReportAllocs()
logDir := testutil.TestTempDir(b)
logFile := filepath.Join(logDir, "test.log")
log := testutil.TestOpenFile(b, logFile)
ctx, cancel := context.WithCancel(context.Background())
waker, awaken := waker.NewTest(ctx, 1)
store := metrics.NewStore()
programFile := filepath.Join("../..", bm.programfile)
mtail, err := mtail.New(ctx, store, mtail.ProgramPath(programFile), mtail.LogPathPatterns(log.Name()), mtail.LogstreamPollWaker(waker))
testutil.FatalIfErr(b, err)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
testutil.FatalIfErr(b, mtail.Run())
}()
var total int64
b.ResetTimer()
for i := 0; i < b.N; i++ {
l, err := os.Open(bm.logfile)
testutil.FatalIfErr(b, err)
count, err := io.Copy(log, l)
testutil.FatalIfErr(b, err)
total += count
awaken(1)
}
cancel()
wg.Wait()
b.StopTimer()
b.SetBytes(total)
})
}
}
// Two mtails both alike in dignity.
func TestFilePipeStreamComparison(t *testing.T) {
testutil.SkipIfShort(t)
for _, tc := range exampleProgramTests {
tc := tc
t.Run(fmt.Sprintf("%s on %s", tc.programfile, tc.logfile),
testutil.TimeoutTest(exampleTimeout, func(t *testing.T) { //nolint:thelper
ctx, cancel := context.WithCancel(context.Background())
waker := waker.NewTestAlways()
fileStore, pipeStore := metrics.NewStore(), metrics.NewStore()
programFile := filepath.Join("../..", tc.programfile)
// Set up the pipe
tmpDir := testutil.TestTempDir(t)
pipeName := filepath.Join(tmpDir, filepath.Base(tc.logfile))
testutil.FatalIfErr(t, unix.Mkfifo(pipeName, 0600))
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
source, err := os.OpenFile(tc.logfile, os.O_RDONLY, 0)
testutil.FatalIfErr(t, err)
// not NONBLOCK to wait for pipeMtail to start reading the pipe
pipe, err := os.OpenFile(pipeName, os.O_WRONLY, os.ModeNamedPipe)
testutil.FatalIfErr(t, err)
n, err := io.Copy(pipe, source)
testutil.FatalIfErr(t, err)
glog.Infof("Copied %d bytes into pipe", n)
source.Close()
pipe.Close()
}()
go func() {
defer wg.Done()
fileMtail, err := mtail.New(ctx, fileStore, mtail.ProgramPath(programFile), mtail.LogPathPatterns(tc.logfile), mtail.OneShot, mtail.OmitMetricSource, mtail.LogPatternPollWaker(waker), mtail.LogstreamPollWaker(waker))
if err != nil {
t.Error(err)
}
if err := fileMtail.Run(); err != nil {
t.Error(err)
}
}()
pipeMtail, err := mtail.New(ctx, pipeStore, mtail.ProgramPath(programFile), mtail.LogPathPatterns(pipeName), mtail.OneShot, mtail.OmitMetricSource, mtail.LogPatternPollWaker(waker), mtail.LogstreamPollWaker(waker))
testutil.FatalIfErr(t, err)
go func() {
defer wg.Done()
if err := pipeMtail.Run(); err != nil {
t.Error(err)
}
}()
// Oneshot mode means we can wait for shutdown before cancelling.
wg.Wait()
cancel()
var pipeMetrics, fileMetrics metrics.MetricSlice
pipeStore.Range(func(m *metrics.Metric) error {
pipeMetrics = append(pipeMetrics, m)
return nil
})
fileStore.Range(func(m *metrics.Metric) error {
fileMetrics = append(fileMetrics, m)
return nil
})
// Ignore the datum.Time field as well, as the results will be unstable otherwise.
testutil.ExpectNoDiff(t, fileMetrics, pipeMetrics, testutil.SortSlices(metrics.Less), testutil.IgnoreUnexported(metrics.Metric{}, sync.RWMutex{}, datum.String{}), testutil.IgnoreFields(datum.BaseDatum{}, "Time"))
}))
}
}
func TestFileSocketStreamComparison(t *testing.T) {
testutil.SkipIfShort(t)
for _, scheme := range []string{"unixgram", "unix"} {
scheme := scheme
for _, tc := range exampleProgramTests {
tc := tc
t.Run(fmt.Sprintf("%s on %s://%s", tc.programfile, scheme, tc.logfile),
testutil.TimeoutTest(exampleTimeout, func(t *testing.T) { //nolint:thelper
ctx, cancel := context.WithCancel(context.Background())
waker := waker.NewTestAlways()
fileStore, sockStore := metrics.NewStore(), metrics.NewStore()
programFile := filepath.Join("../..", tc.programfile)
// Set up the socket
tmpDir := testutil.TestTempDir(t)
sockName := filepath.Join(tmpDir, filepath.Base(tc.logfile))
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
fileMtail, err := mtail.New(ctx, fileStore, mtail.ProgramPath(programFile), mtail.LogPathPatterns(tc.logfile), mtail.OneShot, mtail.OmitMetricSource, mtail.LogPatternPollWaker(waker), mtail.LogstreamPollWaker(waker))
if err != nil {
t.Error(err)
}
if err := fileMtail.Run(); err != nil {
t.Error(err)
}
}()
sockMtail, err := mtail.New(ctx, sockStore, mtail.ProgramPath(programFile), mtail.LogPathPatterns(scheme+"://"+sockName), mtail.OneShot, mtail.OmitMetricSource, mtail.LogPatternPollWaker(waker), mtail.LogstreamPollWaker(waker))
testutil.FatalIfErr(t, err)
go func() {
defer wg.Done()
if err := sockMtail.Run(); err != nil {
t.Error(err)
}
}()
go func() {
defer wg.Done()
source, err := os.OpenFile(tc.logfile, os.O_RDONLY, 0)
testutil.FatalIfErr(t, err)
s, err := net.DialUnix(scheme, nil, &net.UnixAddr{sockName, scheme})
testutil.FatalIfErr(t, err)
n, err := io.Copy(s, source)
testutil.FatalIfErr(t, err)
glog.Infof("Copied %d bytes into socket", n)
if scheme == "unixgram" {
// Write zero bytes after Stop is called to signal that this is the "end of the stream".
_, err = s.Write([]byte{})
testutil.FatalIfErr(t, err)
}
source.Close()
s.Close()
}()
// Oneshot mode means we can wait for shutdown before cancelling.
wg.Wait()
cancel()
var sockMetrics, fileMetrics metrics.MetricSlice
sockStore.Range(func(m *metrics.Metric) error {
sockMetrics = append(sockMetrics, m)
return nil
})
fileStore.Range(func(m *metrics.Metric) error {
fileMetrics = append(fileMetrics, m)
return nil
})
// Ignore the datum.Time field as well, as the results will be unstable otherwise.
testutil.ExpectNoDiff(t, fileMetrics, sockMetrics, testutil.SortSlices(metrics.Less), testutil.IgnoreUnexported(metrics.Metric{}, sync.RWMutex{}, datum.String{}), testutil.IgnoreFields(datum.BaseDatum{}, "Time"))
}))
}
}
}
mtail-3.0.0~rc48/internal/mtail/golden/ 0000775 0000000 0000000 00000000000 14121212652 0017705 5 ustar 00root root 0000000 0000000 mtail-3.0.0~rc48/internal/mtail/golden/reader.go 0000664 0000000 0000000 00000010076 14121212652 0021502 0 ustar 00root root 0000000 0000000 // Copyright 2016 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package golden
import (
"bufio"
"io"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/golang/glog"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
)
var varRe = regexp.MustCompile(`^(counter|gauge|timer|text|histogram) ([^ ]+)(?: {([^}]+)})?(?: (\S+))?(?: (.+))?`)
// ReadTestData loads a "golden" test data file from a programfile and returns as a slice of Metrics.
func ReadTestData(file io.Reader, programfile string) metrics.MetricSlice {
store := metrics.NewStore()
prog := filepath.Base(programfile)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
glog.V(2).Infof("'%s'\n", scanner.Text())
match := varRe.FindStringSubmatch(scanner.Text())
glog.V(2).Infof("len match: %d\n", len(match))
if len(match) == 0 {
continue
}
keys := make([]string, 0)
vals := make([]string, 0)
if match[3] != "" {
for _, pair := range strings.Split(match[3], ",") {
glog.V(2).Infof("pair: %s\n", pair)
kv := strings.Split(pair, "=")
keys = append(keys, kv[0])
if kv[1] != "" {
if kv[1] == `""` {
vals = append(vals, "")
} else {
vals = append(vals, kv[1])
}
}
}
}
var kind metrics.Kind
switch match[1] {
case "counter":
kind = metrics.Counter
case "gauge":
kind = metrics.Gauge
case "timer":
kind = metrics.Timer
case "text":
kind = metrics.Text
case "histogram":
kind = metrics.Histogram
}
glog.V(2).Infof("match[4]: %q", match[4])
typ := metrics.Int
var (
ival int64
fval float64
sval string
err error
)
if match[4] != "" {
ival, err = strconv.ParseInt(match[4], 10, 64)
if err != nil {
fval, err = strconv.ParseFloat(match[4], 64)
typ = metrics.Float
if err != nil || fval == 0.0 {
sval = match[4]
typ = metrics.String
}
}
glog.V(2).Infof("type is %q", typ)
}
var timestamp time.Time
glog.V(2).Infof("match 5: %q\n", match[5])
if match[5] != "" {
timestamp, err = time.Parse(time.RFC3339, match[5])
if err != nil {
j, err := strconv.ParseInt(match[5], 10, 64)
if err == nil {
timestamp = time.Unix(j/1000000000, j%1000000000)
} else {
glog.V(2).Info(err)
}
}
}
glog.V(2).Infof("timestamp is %s which is %v in unix", timestamp.Format(time.RFC3339), timestamp.Unix())
// Now we have enough information to get or create a metric.
m := store.FindMetricOrNil(match[2], prog)
if m != nil {
if m.Type != typ {
glog.V(2).Infof("The type of the fetched metric is not %s: %s", typ, m)
continue
}
} else {
m = metrics.NewMetric(match[2], prog, kind, typ, keys...)
if kind == metrics.Counter && len(keys) == 0 {
d, err := m.GetDatum()
if err != nil {
glog.Fatal(err)
}
// Initialize to zero at the zero time.
switch typ {
case metrics.Int:
datum.SetInt(d, 0, time.Unix(0, 0))
case metrics.Float:
datum.SetFloat(d, 0, time.Unix(0, 0))
}
}
glog.V(2).Infof("making a new %v\n", m)
if err := store.Add(m); err != nil {
glog.Infof("Failed to add metric %v to store: %s", m, err)
}
}
if match[4] != "" {
d, err := m.GetDatum(vals...)
if err != nil {
glog.V(2).Infof("Failed to get datum: %s", err)
continue
}
glog.V(2).Infof("got datum %v", d)
switch typ {
case metrics.Int:
glog.V(2).Infof("setting %v with vals %v to %v at %v\n", d, vals, ival, timestamp)
datum.SetInt(d, ival, timestamp)
case metrics.Float:
glog.V(2).Infof("setting %v with vals %v to %v at %v\n", d, vals, fval, timestamp)
datum.SetFloat(d, fval, timestamp)
case metrics.String:
glog.V(2).Infof("setting %v with vals %v to %v at %v\n", d, vals, sval, timestamp)
datum.SetString(d, sval, timestamp)
}
}
glog.V(2).Infof("Metric is now %s", m)
}
storeList := make([]*metrics.Metric, 0)
/* nolint:errcheck #nosec G104 always returns nil */
store.Range(func(m *metrics.Metric) error {
storeList = append(storeList, m)
return nil
})
return storeList
}
mtail-3.0.0~rc48/internal/mtail/golden/reader_test.go 0000664 0000000 0000000 00000005517 14121212652 0022545 0 ustar 00root root 0000000 0000000 package golden
import (
"os"
"sync"
"testing"
"time"
"github.com/google/mtail/internal/metrics"
"github.com/google/mtail/internal/metrics/datum"
"github.com/google/mtail/internal/testutil"
)
var expectedMetrics = metrics.MetricSlice{
{
Name: "bytes_total",
Program: "reader_test",
Kind: metrics.Counter,
Keys: []string{"operation"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"sent"},
Value: datum.MakeInt(62793673, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC)),
},
{
Labels: []string{"received"},
Value: datum.MakeInt(975017, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC)),
},
},
},
{
Name: "connections_total",
Program: "reader_test",
Kind: metrics.Counter,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: datum.MakeInt(52, time.Date(2011, 2, 22, 21, 54, 13, 0, time.UTC)),
},
},
},
{
Name: "connection-time_total",
Program: "reader_test",
Kind: metrics.Counter,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: datum.MakeInt(1181011, time.Date(2011, 2, 23, 5, 54, 10, 0, time.UTC)),
},
},
},
{
Name: "transfers_total",
Program: "reader_test",
Kind: metrics.Counter,
Keys: []string{"operation", "module"},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{"send", "module"},
Value: datum.MakeInt(2, time.Date(2011, 2, 23, 5, 50, 32, 0, time.UTC)),
},
{
Labels: []string{"send", "repo"},
Value: datum.MakeInt(25, time.Date(2011, 2, 23, 5, 51, 14, 0, time.UTC)),
},
},
},
{
Name: "foo",
Program: "reader_test",
Kind: metrics.Gauge,
Keys: []string{"label"},
LabelValues: []*metrics.LabelValue{},
},
{
Name: "bar",
Program: "reader_test",
Kind: metrics.Counter,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Value: datum.MakeInt(0, time.Unix(0, 0)),
},
},
},
{
Name: "floaty",
Program: "reader_test",
Kind: metrics.Gauge,
Type: metrics.Float,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: datum.MakeFloat(37.1, time.Date(2017, 6, 15, 18, 9, 37, 0, time.UTC)),
},
},
},
{
Name: "stringy",
Program: "reader_test",
Kind: metrics.Text,
Type: metrics.String,
Keys: []string{},
LabelValues: []*metrics.LabelValue{
{
Labels: []string{},
Value: datum.MakeString("hi", time.Date(2018, 6, 16, 18, 04, 0, 0, time.UTC)),
},
},
},
}
func TestReadTestData(t *testing.T) {
f, err := os.Open("reader_test.golden")
testutil.FatalIfErr(t, err)
defer f.Close()
readMetrics := ReadTestData(f, "reader_test")
testutil.ExpectNoDiff(t, expectedMetrics, readMetrics, testutil.SortSlices(metrics.Less), testutil.IgnoreUnexported(metrics.Metric{}, sync.RWMutex{}, datum.String{}))
}
mtail-3.0.0~rc48/internal/mtail/golden/reader_test.golden 0000664 0000000 0000000 00000000773 14121212652 0023407 0 ustar 00root root 0000000 0000000 counter bytes_total {operation=sent} 62793673 2011-02-23T05:54:10Z
counter bytes_total {operation=received} 975017 2011-02-23T05:54:10Z
counter connections_total 52 2011-02-22T21:54:13Z
counter connection-time_total 1181011 2011-02-23T05:54:10Z
counter transfers_total {operation=send,module=module} 2 2011-02-23T05:50:32Z
counter transfers_total {operation=send,module=repo} 25 2011-02-23T05:51:14Z
gauge foo {label=}
counter bar
gauge floaty 37.1 2017-06-15T18:09:37Z
text stringy hi 2018-06-16T18:04:00Z
mtail-3.0.0~rc48/internal/mtail/httpstatus.go 0000664 0000000 0000000 00000003517 14121212652 0021215 0 ustar 00root root 0000000 0000000 // Copyright 2020 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail
import (
"html/template"
"net/http"
"github.com/golang/glog"
)
const statusTemplate = `
mtail on {{.BindAddress}}
mtail on {{.BindAddress}}
Build: {{.BuildInfo}}
Metrics: json, graphite, prometheus, varz
Debug: debug/pprof, debug/vars, tracez, progz
`
// ServeHTTP satisfies the http.Handler interface, and is used to serve the
// root page of mtail for online status reporting.
func (m *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t, err := template.New("status").Parse(statusTemplate)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := struct {
BindAddress string
BuildInfo string
}{
m.listener.Addr().String(),
m.buildInfo.String(),
}
w.Header().Add("Content-type", "text/html")
w.WriteHeader(http.StatusOK)
if err = t.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
err = m.r.WriteStatusHTML(w)
if err != nil {
glog.Warningf("Error while writing loader status: %s", err)
}
err = m.t.WriteStatusHTML(w)
if err != nil {
glog.Warningf("Error while writing tailer status: %s", err)
}
}
// FaviconHandler is used to serve up the favicon.ico for mtail's http server.
func FaviconHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/x-icon")
w.Header().Set("Cache-Control", "public, max-age=7776000")
if _, err := w.Write(logoFavicon); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
mtail-3.0.0~rc48/internal/mtail/log_deletion_integration_test.go 0000664 0000000 0000000 00000001715 14121212652 0025076 0 ustar 00root root 0000000 0000000 // Copyright 2020 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"os"
"path/filepath"
"testing"
"github.com/golang/glog"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestLogDeletion(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
// touch log file
logFilepath := filepath.Join(workdir, "log")
logFile := testutil.TestOpenFile(t, logFilepath)
defer logFile.Close()
m, stopM := mtail.TestStartServer(t, 1, mtail.LogPathPatterns(logFilepath))
defer stopM()
logCloseCheck := m.ExpectMapExpvarDeltaWithDeadline("log_closes_total", logFilepath, 1)
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", -1)
glog.Info("remove")
testutil.FatalIfErr(t, os.Remove(logFilepath))
m.PollWatched(0) // one pass to stop
logCloseCheck()
m.PollWatched(0) // one pass to remove completed stream
logCountCheck()
}
mtail-3.0.0~rc48/internal/mtail/log_glob_integration_test.go 0000664 0000000 0000000 00000011723 14121212652 0024216 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"expvar"
"os"
"path/filepath"
"testing"
"github.com/golang/glog"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestGlobBeforeStart(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
globTests := []struct {
name string
expected bool
}{
{
filepath.Join(workdir, "log1"),
true,
},
{
filepath.Join(workdir, "log2"),
true,
},
{
filepath.Join(workdir, "1log"),
false,
},
}
var count int64
for _, tt := range globTests {
log := testutil.TestOpenFile(t, tt.name)
defer log.Close()
if tt.expected {
count++
}
testutil.WriteString(t, log, "\n")
}
m, stopM := mtail.TestStartServer(t, 0, mtail.LogPathPatterns(filepath.Join(workdir, "log*")))
stopM()
if r := m.GetExpvar("log_count"); r.(*expvar.Int).Value() != count {
t.Errorf("Expecting log count of %d, received %d", count, r)
}
}
func TestGlobAfterStart(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
globTests := []struct {
name string
expected bool
}{
{
filepath.Join(workdir, "log1"),
true,
},
{
filepath.Join(workdir, "log2"),
true,
},
{
filepath.Join(workdir, "1log"),
false,
},
}
m, stopM := mtail.TestStartServer(t, 0, mtail.LogPathPatterns(filepath.Join(workdir, "log*")))
defer stopM()
m.PollWatched(0) // Force sync to EOF
var count int64
for _, tt := range globTests {
if tt.expected {
count++
}
}
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", count)
for _, tt := range globTests {
log := testutil.TestOpenFile(t, tt.name)
m.PollWatched(0) // Force sync to EOF
defer log.Close()
}
// m.PollWatched(2)
logCountCheck()
}
func TestGlobIgnoreFolder(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
globTests := []struct {
name string
isFolder bool
expected bool
}{
{
filepath.Join(workdir, "log1"),
false,
true,
},
{
filepath.Join(workdir, "logarchive"),
true,
false,
},
{
filepath.Join(workdir, "log2.gz"),
false,
false,
},
}
var count int64
for _, tt := range globTests {
var err error
var log *os.File
if tt.isFolder {
err = os.Mkdir(tt.name, 0700)
testutil.FatalIfErr(t, err)
continue
} else {
log, err = os.Create(tt.name)
}
if !tt.isFolder && tt.expected {
count++
}
defer log.Close()
testutil.FatalIfErr(t, err)
testutil.WriteString(t, log, "\n")
}
m, stopM := mtail.TestStartServer(t, 0, mtail.LogPathPatterns(filepath.Join(workdir, "log*")), mtail.IgnoreRegexPattern("\\.gz"))
stopM()
if r := m.GetExpvar("log_count"); r.(*expvar.Int).Value() != count {
t.Errorf("Expecting log count of %d, received %v", count, r)
}
}
func TestFilenameRegexIgnore(t *testing.T) {
testutil.SkipIfShort(t)
workdir := testutil.TestTempDir(t)
globTests := []struct {
name string
expected bool
}{
{
filepath.Join(workdir, "log1"),
true,
},
{
filepath.Join(workdir, "log1.gz"),
false,
},
{
filepath.Join(workdir, "log2gz"),
true,
},
}
var count int64
for _, tt := range globTests {
log, err := os.Create(tt.name)
testutil.FatalIfErr(t, err)
defer log.Close()
if tt.expected {
count++
}
testutil.WriteString(t, log, "\n")
}
m, stopM := mtail.TestStartServer(t, 0, mtail.LogPathPatterns(filepath.Join(workdir, "log*")), mtail.IgnoreRegexPattern("\\.gz"))
stopM()
if r := m.GetExpvar("log_count"); r.(*expvar.Int).Value() != count {
t.Errorf("Log count not matching, expected: %d received: %v", count, r)
}
}
func TestGlobRelativeAfterStart(t *testing.T) {
testutil.SkipIfShort(t)
tmpDir := testutil.TestTempDir(t)
logDir := filepath.Join(tmpDir, "logs")
progDir := filepath.Join(tmpDir, "progs")
err := os.Mkdir(logDir, 0700)
testutil.FatalIfErr(t, err)
err = os.Mkdir(progDir, 0700)
testutil.FatalIfErr(t, err)
// Move to logdir to make relative paths
testutil.Chdir(t, logDir)
m, stopM := mtail.TestStartServer(t, 1, mtail.ProgramPath(progDir), mtail.LogPathPatterns("log.*"))
defer stopM()
{
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 1)
logFile := filepath.Join(logDir, "log.1.txt")
f := testutil.TestOpenFile(t, logFile)
m.PollWatched(1) // Force sync to EOF
testutil.WriteString(t, f, "line 1\n")
m.PollWatched(1)
logCountCheck()
}
{
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 1)
logFile := filepath.Join(logDir, "log.2.txt")
f := testutil.TestOpenFile(t, logFile)
m.PollWatched(2)
testutil.WriteString(t, f, "line 1\n")
m.PollWatched(2)
logCountCheck()
}
{
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 0)
logFile := filepath.Join(logDir, "log.2.txt")
f := testutil.TestOpenFile(t, logFile)
m.PollWatched(2)
testutil.WriteString(t, f, "line 2\n")
m.PollWatched(2)
logCountCheck()
}
glog.Infof("end")
}
mtail-3.0.0~rc48/internal/mtail/log_rotation_integration_test.go 0000664 0000000 0000000 00000010244 14121212652 0025127 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"fmt"
"os"
"path/filepath"
"sync"
"testing"
"github.com/golang/glog"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestLogRotation(t *testing.T) {
testutil.SkipIfShort(t)
for _, tc := range []bool{false, true} {
tc := tc
name := "disabled"
if tc {
name = "enabled"
}
t.Run(fmt.Sprintf("race simulation %s", name), func(t *testing.T) {
tmpDir := testutil.TestTempDir(t)
logDir := filepath.Join(tmpDir, "logs")
progDir := filepath.Join(tmpDir, "progs")
err := os.Mkdir(logDir, 0700)
testutil.FatalIfErr(t, err)
err = os.Mkdir(progDir, 0700)
testutil.FatalIfErr(t, err)
logFile := filepath.Join(logDir, "log")
f := testutil.TestOpenFile(t, logFile)
m, stopM := mtail.TestStartServer(t, 1, mtail.ProgramPath(progDir), mtail.LogPathPatterns(logDir+"/log"))
defer stopM()
logOpensTotalCheck := m.ExpectMapExpvarDeltaWithDeadline("log_opens_total", logFile, 1)
logLinesTotalCheck := m.ExpectMapExpvarDeltaWithDeadline("log_lines_total", logFile, 3)
testutil.WriteString(t, f, "line 1\n")
m.PollWatched(1)
testutil.WriteString(t, f, "line 2\n")
m.PollWatched(1)
logClosedCheck := m.ExpectMapExpvarDeltaWithDeadline("log_closes_total", logFile, 1)
logCompletedCheck := m.ExpectExpvarDeltaWithDeadline("log_count", -1)
glog.Info("rename")
err = os.Rename(logFile, logFile+".1")
testutil.FatalIfErr(t, err)
if tc {
m.PollWatched(0) // simulate race condition with this poll.
logClosedCheck() // sync when filestream closes fd
m.PollWatched(0) // invoke the GC
logCompletedCheck() // sync to when the logstream is removed from tailer
}
glog.Info("create")
f = testutil.TestOpenFile(t, logFile)
m.PollWatched(1)
testutil.WriteString(t, f, "line 1\n")
m.PollWatched(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
logLinesTotalCheck()
}()
go func() {
defer wg.Done()
logOpensTotalCheck()
}()
wg.Wait()
})
}
}
func TestLogSoftLinkChange(t *testing.T) {
testutil.SkipIfShort(t)
for _, tc := range []bool{false, true} {
tc := tc
name := "disabled"
if tc {
name = "enabled"
}
t.Run(fmt.Sprintf("race simulation %s", name), func(t *testing.T) {
workdir := testutil.TestTempDir(t)
logFilepath := filepath.Join(workdir, "log")
m, stopM := mtail.TestStartServer(t, 1, mtail.LogPathPatterns(logFilepath))
defer stopM()
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 1)
logOpensTotalCheck := m.ExpectMapExpvarDeltaWithDeadline("log_opens_total", logFilepath, 2)
trueLog1 := testutil.TestOpenFile(t, logFilepath+".true1")
defer trueLog1.Close()
testutil.FatalIfErr(t, os.Symlink(logFilepath+".true1", logFilepath))
glog.Info("symlinked")
m.PollWatched(1)
inputLines := []string{"hi1", "hi2", "hi3"}
for _, x := range inputLines {
testutil.WriteString(t, trueLog1, x+"\n")
}
m.PollWatched(1)
trueLog2 := testutil.TestOpenFile(t, logFilepath+".true2")
defer trueLog2.Close()
m.PollWatched(1)
logClosedCheck := m.ExpectMapExpvarDeltaWithDeadline("log_closes_total", logFilepath, 1)
logCompletedCheck := m.ExpectExpvarDeltaWithDeadline("log_count", -1)
testutil.FatalIfErr(t, os.Remove(logFilepath))
if tc {
m.PollWatched(0) // simulate race condition with this poll.
logClosedCheck() // sync when filestream closes fd
m.PollWatched(0) // invoke the GC
logCompletedCheck() // sync to when the logstream is removed from tailer
}
testutil.FatalIfErr(t, os.Symlink(logFilepath+".true2", logFilepath))
m.PollWatched(1)
for _, x := range inputLines {
testutil.WriteString(t, trueLog2, x+"\n")
}
m.PollWatched(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
logCountCheck()
}()
go func() {
defer wg.Done()
logOpensTotalCheck()
}()
wg.Wait()
_, err := os.Stat(logFilepath + ".true1")
testutil.FatalIfErr(t, err)
_, err = os.Stat(logFilepath + ".true2")
testutil.FatalIfErr(t, err)
})
}
}
mtail-3.0.0~rc48/internal/mtail/log_truncation_integration_test.go 0000664 0000000 0000000 00000002753 14121212652 0025464 0 ustar 00root root 0000000 0000000 // Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package mtail_test
import (
"os"
"path/filepath"
"testing"
"github.com/golang/glog"
"github.com/google/mtail/internal/mtail"
"github.com/google/mtail/internal/testutil"
)
func TestLogTruncation(t *testing.T) {
testutil.SkipIfShort(t)
tmpDir := testutil.TestTempDir(t)
logDir := filepath.Join(tmpDir, "logs")
progDir := filepath.Join(tmpDir, "progs")
testutil.FatalIfErr(t, os.Mkdir(logDir, 0700))
testutil.FatalIfErr(t, os.Mkdir(progDir, 0700))
m, stopM := mtail.TestStartServer(t, 1, mtail.ProgramPath(progDir), mtail.LogPathPatterns(logDir+"/log"))
defer stopM()
logCountCheck := m.ExpectExpvarDeltaWithDeadline("log_count", 1)
linesCountCheck := m.ExpectExpvarDeltaWithDeadline("lines_total", 2)
logFile := filepath.Join(logDir, "log")
f := testutil.TestOpenFile(t, logFile)
m.PollWatched(1)
testutil.WriteString(t, f, "line 1\n")
m.PollWatched(1)
// After the last barrier, the filestream may not race ahead of the test
// here, so we need to ensure that a whole filestream loop occurs and that
// the file offset advances for this test to succeed, hence the second
// barrier here.
m.PollWatched(1)
err := f.Close()
testutil.FatalIfErr(t, err)
glog.Info("truncate")
f, err = os.OpenFile(logFile, os.O_TRUNC|os.O_WRONLY, 0600)
testutil.FatalIfErr(t, err)
m.PollWatched(1)
testutil.WriteString(t, f, "2\n")
m.PollWatched(1)
linesCountCheck()
logCountCheck()
}
mtail-3.0.0~rc48/internal/mtail/logo.ico 0000664 0000000 0000000 00000076446 14121212652 0020112 0 ustar 00root root 0000000 0000000 @@ (B F 00 % nB h h x ( @ @ ۘ4 ۘ4 ۘ4ڗ3ڗ3 ה0 ۘ4 ۘ4%ۘ4ۘ4ڗ3ܙ5 ۘ4 ڗ3 ۘ4 ۘ4%ۘ4ۘ4ڗ3ڗ3ڗ3 ڗ3 ڗ3 ۘ4 ۘ4%ۘ4ۘ4ۘ4ڗ3ڗ3ڗ3 ۘ4 ۘ4%ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ۘ4 ܙ5 ۘ4 ۘ4 ۘ4%ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ۘ4 ֓/ ۘ4 ۘ4$ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ڗ3 ؕ1 ۘ1 ڙ/ۘ3ۘ4ۘ4!ۘ4Aۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ۘ4 ڗ3 ڗ3 ۠ ܗ; ۙ2ۗ36ۘ3lۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ڗ3ژ3 ڗ3 ۘ3 ۗ3ۙ3*ۘ3ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ڗ3ڗ3ڗ3 ۚ2 ݜ0ۘ3Bۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ۘ4ژ3ژ3ژ3 &