\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 &