From ac7f7c99e9f4d4eb360b64d68df934dbaa670c04 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Fri, 20 Jan 2017 18:05:53 +0100 Subject: [PATCH 01/78] Remove LS 1.x config from docs (#3426) Now that 5.x is relase, we do not need the configs for the 1.x release in the docs anymore. --- libbeat/docs/outputconfig.asciidoc | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/libbeat/docs/outputconfig.asciidoc b/libbeat/docs/outputconfig.asciidoc index c8e24ee93f4..db208e9e0c3 100644 --- a/libbeat/docs/outputconfig.asciidoc +++ b/libbeat/docs/outputconfig.asciidoc @@ -363,8 +363,8 @@ Every event sent to Logstash contains additional metadata for indexing and filte In Logstash, you can configure the Elasticsearch output plugin to use the metadata and event type for indexing. -The following *Logstash 1.5* configuration file sets Logstash to use the index and -document type reported by Beats for indexing events into Elasticsearch. +The following Logstash configuration file for the versions 2.x and 5.x sets Logstash to +use the index and document type reported by Beats for indexing events into Elasticsearch. The index used will depend on the `@timestamp` field as identified by Logstash. [source,logstash] @@ -376,28 +376,6 @@ input { } } -output { - elasticsearch { - host => "localhost" - port => "9200" - protocol => "http" - index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}" - document_type => "%{[@metadata][type]}" - } -} ------------------------------------------------------------------------------- - -Here is the same configuration for *Logstash 2.x* releases: - -[source,logstash] ------------------------------------------------------------------------------- - -input { - beats { - port => 5044 - } -} - output { elasticsearch { hosts => ["http://localhost:9200"] @@ -1240,4 +1218,4 @@ Example configurable that uses the `format` codec to print the events timestamp output.console: codec.format: string: '%{[@timestamp]} %{[message]}' ------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------ From 0a8ca7dd18d221891b4421164a373f753e25a4c2 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Fri, 20 Jan 2017 18:07:18 +0100 Subject: [PATCH 02/78] Introduce simple regex-like matcher (#2433) Provide match.Matcher and match.ExactMatcher using regular expressions for matching use-case only. The matchers compile a regular expression into a Matcher, which only provides the Match functionality. This gives us a chance to optimize/replace some common cases used for matching: - replace capture-groups by non-capturing groups - remove leading/trailing `.*` expressions (Match already searches for sub-string matching the regex) - replace simple literal searches with `==` and `strings.Contains` and `strings.startsWith` - replace regex for alternative literals (e.g. `DEBUG|INFO|ERROR`) with strings.Contains over set of literals - optimized empty-lines checks If input regular expression can not be matched to a simple case, regexp.Regexp will be used. The `ExactMatcher` will embedd `` into `^$` by default. Note: Matcher does currently not split simple cases. e.g. `abc.*def` or `abc.def` will still fallback to regexp.Regexp. --- libbeat/common/match/cmp.go | 257 +++++++++++++++ libbeat/common/match/compile.go | 111 +++++++ libbeat/common/match/matcher.go | 104 ++++++ libbeat/common/match/matcher_bench_test.go | 144 +++++++++ libbeat/common/match/matcher_test.go | 349 +++++++++++++++++++++ libbeat/common/match/matchers.go | 240 ++++++++++++++ libbeat/common/match/optimize.go | 206 ++++++++++++ 7 files changed, 1411 insertions(+) create mode 100644 libbeat/common/match/cmp.go create mode 100644 libbeat/common/match/compile.go create mode 100644 libbeat/common/match/matcher.go create mode 100644 libbeat/common/match/matcher_bench_test.go create mode 100644 libbeat/common/match/matcher_test.go create mode 100644 libbeat/common/match/matchers.go create mode 100644 libbeat/common/match/optimize.go diff --git a/libbeat/common/match/cmp.go b/libbeat/common/match/cmp.go new file mode 100644 index 00000000000..cabf800b7b4 --- /dev/null +++ b/libbeat/common/match/cmp.go @@ -0,0 +1,257 @@ +package match + +import "regexp/syntax" + +// common predefined patterns +var ( + patDotStar = mustParse(`.*`) + patNullBeginDotStar = mustParse(`^.*`) + patNullEndDotStar = mustParse(`.*$`) + + patEmptyText = mustParse(`^$`) + patEmptyWhiteText = mustParse(`^\s*$`) + + // patterns matching any content + patAny1 = patDotStar + patAny2 = mustParse(`^.*`) + patAny3 = mustParse(`^.*$`) + patAny4 = mustParse(`.*$`) + + patBeginText = mustParse(`^`) + patEndText = mustParse(`$`) + + patDigits = mustParse(`\d`) +) + +// isPrefixLiteral checks regular expression being literal checking string +// starting with literal pattern (like '^PATTERN') +func isPrefixLiteral(r *syntax.Regexp) bool { + return r.Op == syntax.OpConcat && + len(r.Sub) == 2 && + r.Sub[0].Op == syntax.OpBeginText && + r.Sub[1].Op == syntax.OpLiteral +} + +func isAltLiterals(r *syntax.Regexp) bool { + if r.Op != syntax.OpAlternate { + return false + } + + for _, sub := range r.Sub { + if sub.Op != syntax.OpLiteral { + return false + } + } + return true +} + +func isExactLiteral(r *syntax.Regexp) bool { + return r.Op == syntax.OpConcat && + len(r.Sub) == 3 && + r.Sub[0].Op == syntax.OpBeginText && + r.Sub[1].Op == syntax.OpLiteral && + r.Sub[2].Op == syntax.OpEndText +} + +func isOneOfLiterals(r *syntax.Regexp) bool { + return r.Op == syntax.OpConcat && + len(r.Sub) == 3 && + r.Sub[0].Op == syntax.OpBeginText && + isAltLiterals(r.Sub[1]) && + r.Sub[2].Op == syntax.OpEndText +} + +// isPrefixAltLiterals checks regular expression being alternative literals +// starting with literal pattern (like '^PATTERN') +func isPrefixAltLiterals(r *syntax.Regexp) bool { + isPrefixAlt := r.Op == syntax.OpConcat && + len(r.Sub) == 2 && + r.Sub[0].Op == syntax.OpBeginText && + r.Sub[1].Op == syntax.OpAlternate + if !isPrefixAlt { + return false + } + + for _, sub := range r.Sub[1].Sub { + if sub.Op != syntax.OpLiteral { + return false + } + } + return true +} + +func isPrefixNumDate(r *syntax.Regexp) bool { + if r.Op != syntax.OpConcat || r.Sub[0].Op != syntax.OpBeginText { + return false + } + + i := 1 + if r.Sub[i].Op == syntax.OpLiteral { + i++ + } + + // check digits + if !isMultiDigits(r.Sub[i]) { + return false + } + i++ + + for i < len(r.Sub) { + // check separator + if r.Sub[i].Op != syntax.OpLiteral { + return false + } + i++ + + // check digits + if !isMultiDigits(r.Sub[i]) { + return false + } + i++ + } + + return true +} + +// isdotStar checks the term being `.*`. +func isdotStar(r *syntax.Regexp) bool { + return eqRegex(r, patDotStar) +} + +func isEmptyText(r *syntax.Regexp) bool { + return eqRegex(r, patEmptyText) +} + +func isEmptyTextWithWhitespace(r *syntax.Regexp) bool { + return eqRegex(r, patEmptyWhiteText) +} + +func isAnyMatch(r *syntax.Regexp) bool { + return eqRegex(r, patAny1) || + eqRegex(r, patAny2) || + eqRegex(r, patAny3) || + eqRegex(r, patAny4) +} + +func isDigitMatch(r *syntax.Regexp) bool { + return eqRegex(r, patDigits) +} + +func isMultiDigits(r *syntax.Regexp) bool { + return isConcatRepetition(r) && isDigitMatch(r.Sub[0]) +} + +func isConcatRepetition(r *syntax.Regexp) bool { + if r.Op != syntax.OpConcat { + return false + } + + first := r.Sub[0] + for _, other := range r.Sub { + if other != first { // concat repetitions reuse references => compare pointers + return false + } + } + + return true +} + +func eqRegex(r, proto *syntax.Regexp) bool { + unmatchable := r.Op != proto.Op || r.Flags != proto.Flags || + (r.Min != proto.Min) || (r.Max != proto.Max) || + (len(r.Sub) != len(proto.Sub)) || + (len(r.Rune) != len(proto.Rune)) + + if unmatchable { + return false + } + + for i := range r.Sub { + if !eqRegex(r.Sub[i], proto.Sub[i]) { + return false + } + } + + for i := range r.Rune { + if r.Rune[i] != proto.Rune[i] { + return false + } + } + return true +} + +func eqPrefixAnyRegex(r *syntax.Regexp, protos ...*syntax.Regexp) bool { + for _, proto := range protos { + if eqPrefixRegex(r, proto) { + return true + } + } + return false +} + +func eqPrefixRegex(r, proto *syntax.Regexp) bool { + if r.Op != syntax.OpConcat { + return false + } + + if proto.Op != syntax.OpConcat { + if len(r.Sub) == 0 { + return false + } + return eqRegex(r.Sub[0], proto) + } + + if len(r.Sub) < len(proto.Sub) { + return false + } + + for i := range proto.Sub { + if !eqRegex(r.Sub[i], proto.Sub[i]) { + return false + } + } + return true +} + +func eqSuffixAnyRegex(r *syntax.Regexp, protos ...*syntax.Regexp) bool { + for _, proto := range protos { + if eqSuffixRegex(r, proto) { + return true + } + } + return false +} + +func eqSuffixRegex(r, proto *syntax.Regexp) bool { + if r.Op != syntax.OpConcat { + return false + } + + if proto.Op != syntax.OpConcat { + i := len(r.Sub) - 1 + if i < 0 { + return false + } + return eqRegex(r.Sub[i], proto) + } + + if len(r.Sub) < len(proto.Sub) { + return false + } + + d := len(r.Sub) - len(proto.Sub) + for i := range proto.Sub { + if !eqRegex(r.Sub[d+i], proto.Sub[i]) { + return false + } + } + return true +} + +func mustParse(pattern string) *syntax.Regexp { + r, err := syntax.Parse(pattern, syntax.Perl) + if err != nil { + panic(err) + } + return r +} diff --git a/libbeat/common/match/compile.go b/libbeat/common/match/compile.go new file mode 100644 index 00000000000..dc0b663b039 --- /dev/null +++ b/libbeat/common/match/compile.go @@ -0,0 +1,111 @@ +package match + +import ( + "regexp" + "regexp/syntax" +) + +func compile(r *syntax.Regexp) (stringMatcher, error) { + switch { + case r.Op == syntax.OpLiteral: + s := string(r.Rune) + return &substringMatcher{s, []byte(s)}, nil + + case isExactLiteral(r): + s := string(r.Sub[1].Rune) + return &equalsMatcher{s, []byte(s)}, nil + + case isAltLiterals(r): + var literals [][]byte + for _, sub := range r.Sub { + literals = append(literals, []byte(string(sub.Rune))) + } + return &altSubstringMatcher{literals}, nil + + case isOneOfLiterals(r): + var literals [][]byte + for _, sub := range r.Sub[1].Sub { + literals = append(literals, []byte(string(sub.Rune))) + } + return &oneOfMatcher{literals}, nil + + case isPrefixLiteral(r): + s := []byte(string(r.Sub[1].Rune)) + return &prefixMatcher{s}, nil + + case isPrefixAltLiterals(r): + var literals [][]byte + for _, sub := range r.Sub[1].Sub { + literals = append(literals, []byte(string(sub.Rune))) + } + return &altPrefixMatcher{literals}, nil + + case isPrefixNumDate(r): + return compilePrefixNumDate(r) + + case isEmptyText(r): + var m *emptyStringMatcher + return m, nil + + case isEmptyTextWithWhitespace(r): + var m *emptyWhiteStringMatcher + return m, nil + + case isAnyMatch(r): + var m *matchAny + return m, nil + + default: + + r, err := regexp.Compile(r.String()) + if err != nil { + return nil, err + } + return r, nil + } +} + +func compilePrefixNumDate(r *syntax.Regexp) (stringMatcher, error) { + m := &prefixNumDate{} + + i := 1 + if r.Sub[i].Op == syntax.OpLiteral { + m.prefix = []byte(string(r.Sub[i].Rune)) + i++ + } + + digitLen := func(r *syntax.Regexp) int { + if r.Op == syntax.OpConcat { + return len(r.Sub) + } + return 1 + } + + var digits []int + var seps [][]byte + + digits = append(digits, digitLen(r.Sub[i])) + i++ + + for i < len(r.Sub) { + seps = append(seps, []byte(string(r.Sub[i].Rune))) + i++ + + digits = append(digits, digitLen(r.Sub[i])) + i++ + } + + minLen := len(m.prefix) + for _, d := range digits { + minLen += d + } + for _, sep := range seps { + minLen += len(sep) + } + + m.digits = digits + m.seps = seps + m.minLen = minLen + + return m, nil +} diff --git a/libbeat/common/match/matcher.go b/libbeat/common/match/matcher.go new file mode 100644 index 00000000000..b848e198135 --- /dev/null +++ b/libbeat/common/match/matcher.go @@ -0,0 +1,104 @@ +package match + +import "regexp/syntax" + +type Matcher struct { + stringMatcher +} + +type ExactMatcher struct { + stringMatcher +} + +type stringMatcher interface { + // MatchString tries to find a matching substring. + MatchString(s string) (matched bool) + + // Match tries to find a matching substring. + Match(bs []byte) (matched bool) + + // Describe the generator + String() string +} + +func MustCompile(pattern string) Matcher { + m, err := Compile(pattern) + if err != nil { + panic(err) + } + return m +} + +func MustCompileExact(pattern string) ExactMatcher { + m, err := CompileExact(pattern) + if err != nil { + panic(err) + } + return m +} + +// Compile regular expression to string matcher. String matcher by default uses +// regular expressions as provided by regexp library, but tries to optimize some +// common cases, replacing expensive patterns with cheaper custom implementations +// or removing terms not necessary for string matching. +func Compile(pattern string) (Matcher, error) { + regex, err := syntax.Parse(pattern, syntax.Perl) + if err != nil { + return Matcher{}, err + } + + regex = optimize(regex).Simplify() + m, err := compile(regex) + return Matcher{m}, err +} + +func CompileExact(pattern string) (ExactMatcher, error) { + regex, err := syntax.Parse(pattern, syntax.Perl) + if err != nil { + return ExactMatcher{}, err + } + + regex = regex.Simplify() + if regex.Op != syntax.OpConcat { + regex = &syntax.Regexp{ + Op: syntax.OpConcat, + Sub: []*syntax.Regexp{ + patBeginText, + regex, + patEndText, + }, + Flags: regex.Flags, + } + } else { + if !eqPrefixRegex(regex, patBeginText) { + regex.Sub = append([]*syntax.Regexp{patBeginText}, regex.Sub...) + } + if !eqSuffixRegex(regex, patEndText) { + regex.Sub = append(regex.Sub, patEndText) + } + } + + regex = optimize(regex).Simplify() + m, err := compile(regex) + return ExactMatcher{m}, err +} + +func (m *Matcher) Unpack(s string) error { + tmp, err := Compile(s) + if err != nil { + return err + } + + *m = tmp + return nil +} + +func (m *ExactMatcher) Unpack(s string) error { + tmp, err := CompileExact(s) + if err != nil { + return err + } + + *m = tmp + return nil +} diff --git a/libbeat/common/match/matcher_bench_test.go b/libbeat/common/match/matcher_bench_test.go new file mode 100644 index 00000000000..41f3d408f1a --- /dev/null +++ b/libbeat/common/match/matcher_bench_test.go @@ -0,0 +1,144 @@ +package match + +import ( + "bytes" + "fmt" + "regexp" + "testing" +) + +type testContent struct { + name string + lines [][]byte +} + +type benchRunner struct { + title string + f func(*testing.B) +} + +var allContents = []testContent{ + mixedContent, + logContent, + logContent2, + logContentLevel, +} + +var mixedContent = makeContent("mixed", `Lorem ipsum dolor sit amet, +PATTERN consectetur adipiscing elit. Nam vitae turpis augue. + Quisque euismod erat tortor, posuere auctor elit fermentum vel. Proin in odio + +23-08-2016 eleifend, maximus turpis non, lacinia ligula. Nullam vel pharetra quam, id egestas + +massa. Sed a vestibulum libero. Sed tellus lorem, imperdiet non nisl ac, + aliquet placerat magna. Sed PATTERN in bibendum eros. Curabitur ut pretium neque. Sed +23-08-2016 egestas elit et leo consectetur, nec dignissim arcu ultricies. Sed molestie tempor + +erat, a maximus sapien rutrum ut. Curabitur congue condimentum dignissim. + Mauris hendrerit, velit nec accumsan egestas, augue justo tincidunt risus, + +a facilisis nulla augue PATTERN eu metus. Duis vel neque sit amet nunc elementum viverra +eu ut ligula. Mauris et libero lacus.`) + +var logContent = makeContent("simple_log", `23-08-2016 15:10:01 - Lorem ipsum dolor sit amet, +23-08-2016 15:10:02 - PATTERN consectetur adipiscing elit. Nam vitae turpis augue. +23-08-2016 15:10:03 - Quisque euismod erat tortor, posuere auctor elit fermentum vel. Proin in odio +23-08-2016 15:10:05 - 23-08-2016 eleifend, maximus turpis non, lacinia ligula. Nullam vel pharetra quam, id egestas +23-08-2016 15:10:07 - massa. Sed a vestibulum libero. Sed tellus lorem, imperdiet non nisl ac, +23-08-2016 15:10:08 - aliquet placerat magna. Sed PATTERN in bibendum eros. Curabitur ut pretium neque. Sed +23-08-2016 15:10:09 - 23-08-2016 egestas elit et leo consectetur, nec dignissim arcu ultricies. Sed molestie tempor +23-08-2016 15:10:11 - erat, a maximus sapien rutrum ut. Curabitur congue condimentum dignissim. +23-08-2016 15:10:12 - Mauris hendrerit, velit nec accumsan egestas, augue justo tincidunt risus, +23-08-2016 15:10:14 - a facilisis nulla augue PATTERN eu metus. Duis vel neque sit amet nunc elementum viverra +eu ut ligula. Mauris et libero lacus. +`) + +var logContent2 = makeContent("simple_log2", `2016-08-23 15:10:01 - DEBUG - Lorem ipsum dolor sit amet, +2016-08-23 15:10:02 - INFO - PATTERN consectetur adipiscing elit. Nam vitae turpis augue. +2016-08-23 15:10:03 - DEBUG - Quisque euismod erat tortor, posuere auctor elit fermentum vel. Proin in odio +2016-08-23 15:10:05 - ERROR - 23-08-2016 eleifend, maximus turpis non, lacinia ligula. Nullam vel pharetra quam, id egestas +2016-08-23 15:10:07 - WARN - massa. Sed a vestibulum libero. Sed tellus lorem, imperdiet non nisl ac, +2016-08-23 15:10:08 - CRIT - aliquet placerat magna. Sed PATTERN in bibendum eros. Curabitur ut pretium neque. Sed +2016-08-23 15:10:09 - DEBUG - 23-08-2016 egestas elit et leo consectetur, nec dignissim arcu ultricies. Sed molestie tempor +2016-08-23 15:10:11 - ERROR - erat, a maximus sapien rutrum ut. Curabitur congue condimentum dignissim. +2016-08-23 15:10:12 - INFO - Mauris hendrerit, velit nec accumsan egestas, augue justo tincidunt risus, +2016-08-23 15:10:14 - INFO - a facilisis nulla augue PATTERN eu metus. Duis vel neque sit amet nunc elementum viverra eu ut ligula. Mauris et libero lacus. +`) + +var logContentLevel = makeContent("simple_log_with_level", `DEBUG - 2016-08-23 15:10:01 - Lorem ipsum dolor sit amet, +INFO - 2016-08-23 15:10:02 - PATTERN consectetur adipiscing elit. Nam vitae turpis augue. +DEBUG - 2016-08-23 15:10:03 - Quisque euismod erat tortor, posuere auctor elit fermentum vel. Proin in odio +ERROR - 2016-08-23 15:10:05 - 23-08-2016 eleifend, maximus turpis non, lacinia ligula. Nullam vel pharetra quam, id egestas +WARN - 2016-08-23 15:10:07 - massa. Sed a vestibulum libero. Sed tellus lorem, imperdiet non nisl ac, +CRIT - 2016-08-23 15:10:08 - aliquet placerat magna. Sed PATTERN in bibendum eros. Curabitur ut pretium neque. Sed +DEBUG - 2016-08-23 15:10:09 - 23-08-2016 egestas elit et leo consectetur, nec dignissim arcu ultricies. Sed molestie tempor +ERROR -2016-08-23 15:10:11 - erat, a maximus sapien rutrum ut. Curabitur congue condimentum dignissim. +DEBUG - 2016-08-23 15:10:12 - Mauris hendrerit, velit nec accumsan egestas, augue justo tincidunt risus, +INFO - 2016-08-23 15:10:14 - a facilisis nulla augue PATTERN eu metus. Duis vel neque sit amet nunc elementum viverra +eu ut ligula. Mauris et libero lacus. +`) + +func BenchmarkPatterns(b *testing.B) { + patterns := []struct { + title string + regex string + }{ + {"match any 1", `^.*$`}, + {"match any 2", `.*`}, + {"startsWith 'PATTERN'", `^PATTERN`}, + {"startsWith ' '", `^ `}, + {"startsWithDate", `^\d{2}-\d{2}-\d{4}`}, + {"startsWithDate2", `^\d{4}-\d{2}-\d{2}`}, + {"startsWithDate3", `^20\d{2}-\d{2}-\d{2}`}, + {"startsWithLevel", `^(DEBUG|INFO|WARN|ERR|CRIT)`}, + {"hasLevel", `(DEBUG|INFO|WARN|ERR|CRIT)`}, + {"contains 'PATTERN'", `PATTERN`}, + {"contains 'PATTERN' with '.*", `.*PATTERN.*`}, + {"empty line", `^$`}, + {"empty line with optional whitespace", `^\s*$`}, + } + + runTitle := func(matcher, name, content string) string { + return fmt.Sprintf("Name=%v, Matcher=%v, Content=%v", name, matcher, content) + } + + for i, pattern := range patterns { + b.Logf("benchmark (%v): %v", i, pattern.title) + + regex := regexp.MustCompile(pattern.regex) + matcher := MustCompile(pattern.regex) + + b.Logf("regex: %v", regex) + b.Logf("matcher: %v", matcher) + + for _, content := range allContents { + title := runTitle("Regex", pattern.title, content.name) + runner := makeRunner(title, content.lines, regex.Match) + b.Run(runner.title, runner.f) + + title = runTitle("Match", pattern.title, content.name) + runner = makeRunner(title, content.lines, matcher.Match) + b.Run(runner.title, runner.f) + } + } +} + +func makeRunner(title string, content [][]byte, m func([]byte) bool) benchRunner { + return benchRunner{ + title, + func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, line := range content { + m(line) + } + } + }, + } +} + +func makeContent(name, s string) testContent { + return testContent{ + name, + bytes.Split([]byte(s), []byte("\n")), + } +} diff --git a/libbeat/common/match/matcher_test.go b/libbeat/common/match/matcher_test.go new file mode 100644 index 00000000000..9e15b1eca56 --- /dev/null +++ b/libbeat/common/match/matcher_test.go @@ -0,0 +1,349 @@ +package match + +import ( + "reflect" + "testing" +) + +func TestMatchers(t *testing.T) { + typeOf := func(v interface{}) reflect.Type { + return reflect.TypeOf(v) + } + + tests := []struct { + pattern string + matcherType reflect.Type + matches []string + noMatches []string + }{ + { + `.*`, + typeOf((*matchAny)(nil)), + []string{ + "any matches always", + }, + nil, + }, + { + `^$`, + typeOf((*emptyStringMatcher)(nil)), + []string{""}, + []string{"not empty"}, + }, + { + `^\s*$`, + typeOf((*emptyWhiteStringMatcher)(nil)), + []string{"", " ", " ", "\t", "\n"}, + []string{"not empty"}, + }, + { + `substring`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `^.*substring`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `substring.*$`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `^.*substring.*$`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `^equals$`, + typeOf((*equalsMatcher)(nil)), + []string{"equals"}, + []string{"not equals"}, + }, + { + `(alt|substring)`, + typeOf((*altSubstringMatcher)(nil)), + []string{ + "has alt in middle", + "alt at beginning", + "uses substring", + }, + []string{"missing sub-string"}, + }, + { + `alt|substring`, + typeOf((*altSubstringMatcher)(nil)), + []string{ + "has alt in middle", + "alt at beginning", + "uses substring", + }, + []string{"missing sub-string"}, + }, + { + `^prefix`, + typeOf((*prefixMatcher)(nil)), + []string{"prefix string match"}, + []string{"missing prefix string"}, + }, + { + `^(DEBUG|INFO|ERROR)`, + typeOf((*altPrefixMatcher)(nil)), + []string{ + "DEBUG - should match", + "INFO - should match too", + "ERROR - yep", + }, + []string{ + "This should not match", + }, + }, + { + `^\d{4}-\d{2}-\d{2}`, + typeOf((*prefixNumDate)(nil)), + []string{ + "2017-01-02 should match", + "2017-01-03 should also match", + }, + []string{ + "- 2017-01-02 should not match", + "fail", + }, + }, + { + `^20\d{2}-\d{2}-\d{2}`, + typeOf((*prefixNumDate)(nil)), + []string{ + "2017-01-02 should match", + "2017-01-03 should also match", + }, + []string{ + "- 2017-01-02 should not match", + "fail", + }, + }, + { + `^20\d{2}-\d{2}-\d{2} \d{2}:\d{2}`, + typeOf((*prefixNumDate)(nil)), + []string{ + "2017-01-02 10:10 should match", + "2017-01-03 10:11 should also match", + }, + []string{ + "- 2017-01-02 should not match", + "fail", + }, + }, + } + + for i, test := range tests { + t.Logf("run test (%v): %v", i, test.pattern) + + matcher, err := Compile(test.pattern) + if err != nil { + t.Error(err) + continue + } + + t.Logf(" matcher: %v", matcher) + + matcherType := reflect.TypeOf(matcher.stringMatcher) + if matcherType != test.matcherType { + t.Errorf(" Matcher type mismatch (expected=%v, actual=%v)", + test.matcherType, + matcherType, + ) + } + + for _, content := range test.matches { + if !matcher.MatchString(content) { + t.Errorf(" failed to match string: '%v'", content) + continue + } + + if !matcher.Match([]byte(content)) { + t.Errorf(" failed to match byte string: '%v'", content) + continue + } + } + + for _, content := range test.noMatches { + if matcher.MatchString(content) { + t.Errorf(" should not match string: '%v'", content) + continue + } + + if matcher.Match([]byte(content)) { + t.Errorf(" should not match string: '%v'", content) + continue + } + } + } +} + +func TestExactMatchers(t *testing.T) { + typeOf := func(v interface{}) reflect.Type { + return reflect.TypeOf(v) + } + + tests := []struct { + pattern string + matcherType reflect.Type + matches []string + noMatches []string + }{ + { + `.*`, + typeOf((*matchAny)(nil)), + []string{ + "any matches always", + }, + nil, + }, + { + `^$`, + typeOf((*emptyStringMatcher)(nil)), + []string{""}, + []string{"not empty"}, + }, + { + `^\s*$`, + typeOf((*emptyWhiteStringMatcher)(nil)), + []string{"", " ", " ", "\t", "\n"}, + []string{"not empty"}, + }, + { + `.*substring.*`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `^.*substring.*`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `.*substring.*$`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `^.*substring.*$`, + typeOf((*substringMatcher)(nil)), + []string{ + "has substring in middle", + "substring at beginning", + "ends with substring", + }, + []string{"missing sub-string"}, + }, + { + `equals`, + typeOf((*equalsMatcher)(nil)), + []string{"equals"}, + []string{"not equals"}, + }, + { + `^equals`, + typeOf((*equalsMatcher)(nil)), + []string{"equals"}, + []string{"not equals"}, + }, + { + `equals$`, + typeOf((*equalsMatcher)(nil)), + []string{"equals"}, + []string{"not equals"}, + }, + { + `DEBUG|INFO`, + typeOf((*oneOfMatcher)(nil)), + []string{ + "DEBUG", + "INFO", + }, + []string{"none"}, + }, + } + + for i, test := range tests { + t.Logf("run test (%v): %v", i, test.pattern) + + matcher, err := CompileExact(test.pattern) + if err != nil { + t.Error(err) + continue + } + + t.Logf(" matcher: %v", matcher) + + matcherType := reflect.TypeOf(matcher.stringMatcher) + if matcherType != test.matcherType { + t.Errorf(" Matcher type mismatch (expected=%v, actual=%v)", + test.matcherType, + matcherType, + ) + } + + for _, content := range test.matches { + if !matcher.MatchString(content) { + t.Errorf(" failed to match string: '%v'", content) + continue + } + + if !matcher.Match([]byte(content)) { + t.Errorf(" failed to match byte string: '%v'", content) + continue + } + } + + for _, content := range test.noMatches { + if matcher.MatchString(content) { + t.Errorf(" should not match string: '%v'", content) + continue + } + + if matcher.Match([]byte(content)) { + t.Errorf(" should not match string: '%v'", content) + continue + } + } + } +} diff --git a/libbeat/common/match/matchers.go b/libbeat/common/match/matchers.go new file mode 100644 index 00000000000..7ec6813b883 --- /dev/null +++ b/libbeat/common/match/matchers.go @@ -0,0 +1,240 @@ +package match + +import ( + "bytes" + "fmt" + "reflect" + "strings" + "unsafe" +) + +type equalsMatcher struct { + s string + bs []byte +} + +type substringMatcher struct { + s string + bs []byte +} + +type altSubstringMatcher struct { + literals [][]byte +} + +type oneOfMatcher struct { + literals [][]byte +} + +type prefixMatcher struct { + s []byte +} + +type altPrefixMatcher struct { + literals [][]byte +} + +type prefixNumDate struct { + minLen int + digits []int + prefix []byte + seps [][]byte +} + +type emptyStringMatcher struct{} + +type emptyWhiteStringMatcher struct{} + +type matchAny struct{} + +func (m *equalsMatcher) MatchString(s string) bool { + return m.s == s +} + +func (m *equalsMatcher) Match(bs []byte) bool { + return bytes.Equal(bs, m.bs) +} + +func (m *equalsMatcher) String() string { + return fmt.Sprintf("", m.s) +} + +func (m *substringMatcher) MatchString(s string) bool { + return strings.Contains(s, m.s) +} + +func (m *substringMatcher) Match(bs []byte) bool { + return bytes.Contains(bs, m.bs) +} + +func (m *substringMatcher) String() string { + return fmt.Sprintf("", m.s) +} + +func (m *altSubstringMatcher) MatchString(s string) bool { + return m.Match(stringToBytes(s)) +} + +func (m *altSubstringMatcher) Match(in []byte) bool { + for _, literal := range m.literals { + if bytes.Contains(in, literal) { + return true + } + } + return false +} + +func (m *altSubstringMatcher) String() string { + return fmt.Sprintf("", bytes.Join(m.literals, []byte(","))) +} + +func (m *oneOfMatcher) MatchString(s string) bool { + return m.Match(stringToBytes(s)) +} + +func (m *oneOfMatcher) Match(in []byte) bool { + for _, literal := range m.literals { + if bytes.Equal(in, literal) { + return true + } + } + return false +} + +func (m *oneOfMatcher) String() string { + return fmt.Sprintf("", bytes.Join(m.literals, []byte(","))) +} + +func (m *prefixMatcher) MatchString(s string) bool { + return len(s) >= len(m.s) && s[0:len(m.s)] == string(m.s) +} + +func (m *prefixMatcher) Match(bs []byte) bool { + return len(bs) >= len(m.s) && bytes.Equal(bs[0:len(m.s)], m.s) +} + +func (m *prefixMatcher) String() string { + return fmt.Sprintf("", string(m.s)) +} + +func (m *altPrefixMatcher) MatchString(in string) bool { + for _, s := range m.literals { + if len(in) >= len(s) && in[0:len(s)] == string(s) { + return true + } + } + return false +} + +func (m *altPrefixMatcher) Match(bs []byte) bool { + for _, s := range m.literals { + if len(bs) >= len(s) && bytes.Equal(bs[0:len(s)], s) { + return true + } + } + return false +} + +func (m *altPrefixMatcher) String() string { + return fmt.Sprintf("", bytes.Join(m.literals, []byte(","))) +} + +func (m *prefixNumDate) MatchString(in string) bool { + return m.Match(stringToBytes(in)) +} + +func (m *prefixNumDate) Match(in []byte) bool { + if len(in) < m.minLen { + return false + } + + pos := 0 + if m.prefix != nil { + end := len(m.prefix) + if !bytes.Equal(in[0:end], m.prefix) { + return false + } + + pos += end + } + + for cnt := m.digits[0]; cnt > 0; cnt-- { + v := in[pos] + pos++ + if !('0' <= v && v <= '9') { + return false + } + } + + for i := 1; i < len(m.digits); i++ { + sep := m.seps[i-1] + if !bytes.Equal(in[pos:pos+len(sep)], sep) { + return false + } + + pos += len(sep) + for cnt := m.digits[i]; cnt > 0; cnt-- { + v := in[pos] + pos++ + if !('0' <= v && v <= '9') { + return false + } + } + } + + return true +} + +func (m *prefixNumDate) String() string { + return "" +} + +func (m *emptyStringMatcher) MatchString(s string) bool { + return len(s) == 0 +} + +func (m *emptyStringMatcher) Match(bs []byte) bool { + return len(bs) == 0 +} + +func (m *emptyStringMatcher) String() string { + return "" +} + +func (m *emptyWhiteStringMatcher) MatchString(s string) bool { + for _, r := range s { + if !(r == 0x9 || r == 0xa || r == 0xc || r == 0xd || r == 0x20 || r == '\t') { + return false + } + } + return true +} + +func (m *emptyWhiteStringMatcher) Match(bs []byte) bool { + for _, r := range bytesToString(bs) { + if !(r == 0x9 || r == 0xa || r == 0xc || r == 0xd || r == 0x20 || r == '\t') { + return false + } + } + return true +} + +func (m *emptyWhiteStringMatcher) String() string { + return "" +} + +func (m *matchAny) Match(_ []byte) bool { return true } +func (m *matchAny) MatchString(_ string) bool { return true } +func (m *matchAny) String() string { return "" } + +func bytesToString(b []byte) string { + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := reflect.StringHeader{Data: bh.Data, Len: bh.Len} + return *(*string)(unsafe.Pointer(&sh)) +} + +func stringToBytes(s string) []byte { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := reflect.SliceHeader{Data: sh.Data, Len: sh.Len, Cap: sh.Len} + return *(*[]byte)(unsafe.Pointer(&bh)) +} diff --git a/libbeat/common/match/optimize.go b/libbeat/common/match/optimize.go new file mode 100644 index 00000000000..cb485c63397 --- /dev/null +++ b/libbeat/common/match/optimize.go @@ -0,0 +1,206 @@ +package match + +import "regexp/syntax" + +type trans func(*syntax.Regexp) (bool, *syntax.Regexp) + +var transformations = []trans{ + simplify, + uncapture, + trimLeft, + trimRight, + unconcat, + concatRepetition, +} + +// optimize runs minimal regular expression optimizations +// until fix-point. +func optimize(r *syntax.Regexp) *syntax.Regexp { + for { + changed := false + for _, t := range transformations { + var upd bool + upd, r = t(r) + changed = changed || upd + } + + if changed == false { + return r + } + } +} + +// Simplify regular expression by stdlib. +func simplify(r *syntax.Regexp) (bool, *syntax.Regexp) { + return false, r.Simplify() +} + +// uncapture optimizes regular expression by removing capture groups from +// regular expression potentially allocating memory when executed. +func uncapture(r *syntax.Regexp) (bool, *syntax.Regexp) { + if r.Op == syntax.OpCapture { + // try to uncapture + if len(r.Sub) == 1 { + _, sub := uncapture(r.Sub[0]) + return true, sub + } + + tmp := *r + tmp.Op = syntax.OpConcat + r = &tmp + } + + sub := make([]*syntax.Regexp, len(r.Sub)) + modified := false + for i := range r.Sub { + var m bool + m, sub[i] = uncapture(r.Sub[i]) + modified = modified || m + } + + if !modified { + return false, r + } + + tmp := *r + tmp.Sub = sub + return true, &tmp +} + +// trimLeft removes not required '.*' from beginning of regular expressions. +func trimLeft(r *syntax.Regexp) (bool, *syntax.Regexp) { + if eqPrefixAnyRegex(r, patDotStar, patNullBeginDotStar) { + tmp := *r + tmp.Sub = tmp.Sub[1:] + return true, &tmp + } + + return false, r +} + +// trimRight removes not required '.*' from end of regular expressions. +func trimRight(r *syntax.Regexp) (bool, *syntax.Regexp) { + if eqSuffixAnyRegex(r, patDotStar, patNullEndDotStar) { + i := len(r.Sub) - 1 + tmp := *r + tmp.Sub = tmp.Sub[0:i] + return true, &tmp + } + + return false, r +} + +// unconcat removes intermediate regular expression concatenations generated by +// parser if concatenation contains only 1 element. Removal of object from +// parse-tree can enable other optimization to fire. +func unconcat(r *syntax.Regexp) (bool, *syntax.Regexp) { + switch { + case r.Op == syntax.OpConcat && len(r.Sub) <= 1: + if len(r.Sub) == 1 { + return true, r.Sub[0] + } + + return true, &syntax.Regexp{ + Op: syntax.OpEmptyMatch, + Flags: r.Flags, + } + + case r.Op == syntax.OpRepeat && r.Min == r.Max && r.Min == 1: + return true, r.Sub[0] + } + + return false, r +} + +// concatRepetition concatenates multiple repeated sub-patterns into +// a repetition of exactly N. +func concatRepetition(r *syntax.Regexp) (bool, *syntax.Regexp) { + + if r.Op != syntax.OpConcat { + // don't iterate sub-expressions if top-level is no OpConcat + return false, r + } + + // check if concatenated op is already a repetition + if isConcatRepetition(r) { + return false, r + } + + // concatenate repetitions in sub-expressions first + var subs []*syntax.Regexp + changed := false + for _, sub := range r.Sub { + changedSub, tmp := concatRepetition(sub) + changed = changed || changedSub + subs = append(subs, tmp) + } + + var concat []*syntax.Regexp + lastMerged := -1 + for i, j := 0, 1; j < len(subs); i, j = j, j+1 { + if subs[i].Op == syntax.OpRepeat && eqRegex(subs[i].Sub[0], subs[j]) { + r := subs[i] + concat = append(concat, + &syntax.Regexp{ + Op: syntax.OpRepeat, + Sub: r.Sub, + Min: r.Min + 1, + Max: r.Max + 1, + Flags: r.Flags, + }, + ) + + lastMerged = j + changed = true + j++ + continue + } + + if isConcatRepetition(subs[i]) && eqRegex(subs[i].Sub[0], subs[j]) { + r := subs[i] + concat = append(concat, + &syntax.Regexp{ + Op: syntax.OpConcat, + Sub: append(r.Sub, r.Sub[0]), + Flags: r.Flags, + }, + ) + + lastMerged = j + changed = true + j++ + continue + } + + if eqRegex(subs[i], subs[j]) { + r := subs[i] + concat = append(concat, + &syntax.Regexp{ + Op: syntax.OpRepeat, + Sub: []*syntax.Regexp{r}, + Min: 2, + Max: 2, + Flags: r.Flags, + }, + ) + + lastMerged = j + changed = true + j++ + continue + } + + concat = append(concat, subs[i]) + } + + if lastMerged+1 != len(subs) { + concat = append(concat, subs[len(subs)-1]) + } + + r = &syntax.Regexp{ + Op: syntax.OpConcat, + Sub: concat, + Flags: r.Flags, + } + return changed, r +} From 77cec5af9b0af20a7defae43778c451d1f53c945 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Fri, 20 Jan 2017 19:03:38 +0100 Subject: [PATCH 03/78] Add HTTP helper for Metricsets (#3413) This should simplify the implementation of MetricSets based on HTTP. --- metricbeat/helper/http.go | 85 +++++++++++++++++++ metricbeat/module/apache/status/data.go | 4 +- metricbeat/module/apache/status/status.go | 28 ++---- metricbeat/module/couchbase/bucket/bucket.go | 39 ++------- metricbeat/module/couchbase/bucket/data.go | 5 +- .../module/couchbase/cluster/cluster.go | 38 ++------- metricbeat/module/couchbase/cluster/data.go | 5 +- metricbeat/module/couchbase/node/data.go | 8 +- metricbeat/module/couchbase/node/node.go | 37 ++------ metricbeat/module/nginx/stubstatus/data.go | 8 +- .../module/nginx/stubstatus/stubstatus.go | 28 ++---- .../module/prometheus/collector/collector.go | 26 ++---- metricbeat/module/prometheus/stats/stats.go | 25 ++---- 13 files changed, 139 insertions(+), 197 deletions(-) create mode 100644 metricbeat/helper/http.go diff --git a/metricbeat/helper/http.go b/metricbeat/helper/http.go new file mode 100644 index 00000000000..eb01376d2c1 --- /dev/null +++ b/metricbeat/helper/http.go @@ -0,0 +1,85 @@ +package helper + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/elastic/beats/metricbeat/mb" +) + +type HTTP struct { + base mb.BaseMetricSet + client *http.Client // HTTP client that is reused across requests. +} + +// NewHTTP creates new http helper +func NewHTTP(base mb.BaseMetricSet) *HTTP { + return &HTTP{ + base: base, + client: &http.Client{Timeout: base.Module().Config().Timeout}, + } +} + +// FetchResponse fetches a response for the http metricset. +// It's important that resp.Body has to be closed if this method is used. Before using this method +// check if one of the other Fetch* methods could be used as they ensure that the Body is properly closed. +func (h *HTTP) FetchResponse() (*http.Response, error) { + req, err := http.NewRequest("GET", h.base.HostData().SanitizedURI, nil) + if h.base.HostData().User != "" || h.base.HostData().Password != "" { + req.SetBasicAuth(h.base.HostData().User, h.base.HostData().Password) + } + resp, err := h.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making http request: %v", err) + } + + return resp, nil +} + +// FetchContent makes an HTTP request to the configured url and returns the body content. +func (h *HTTP) FetchContent() ([]byte, error) { + resp, err := h.FetchResponse() + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("HTTP error %d in %s: %s", resp.StatusCode, h.base.Name(), resp.Status) + } + + return ioutil.ReadAll(resp.Body) +} + +// FetchScanner returns a Scanner for the content. +func (h *HTTP) FetchScanner() (*bufio.Scanner, error) { + content, err := h.FetchContent() + if err != nil { + return nil, err + } + + return bufio.NewScanner(bytes.NewReader(content)), nil +} + +// FetchJSON makes an HTTP request to the configured url and returns the JSON content. +// This only works if the JSON output needed is in map[string]interface format. +func (h *HTTP) FetchJSON() (map[string]interface{}, error) { + + body, err := h.FetchContent() + if err != nil { + return nil, err + } + + var data map[string]interface{} + + err = json.Unmarshal(body, &data) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/metricbeat/module/apache/status/data.go b/metricbeat/module/apache/status/data.go index 05d5712bfa2..72fd73e9c8e 100644 --- a/metricbeat/module/apache/status/data.go +++ b/metricbeat/module/apache/status/data.go @@ -2,7 +2,6 @@ package status import ( "bufio" - "io" "regexp" "strings" @@ -55,7 +54,7 @@ var ( ) // Map body to MapStr -func eventMapping(body io.ReadCloser, hostname string) common.MapStr { +func eventMapping(scanner *bufio.Scanner, hostname string) common.MapStr { var ( totalS int totalR int @@ -72,7 +71,6 @@ func eventMapping(body io.ReadCloser, hostname string) common.MapStr { ) fullEvent := map[string]interface{}{} - scanner := bufio.NewScanner(body) // Iterate through all events to gather data for scanner.Scan() { diff --git a/metricbeat/module/apache/status/status.go b/metricbeat/module/apache/status/status.go index 6bba5e5fedd..c11fd8688e0 100644 --- a/metricbeat/module/apache/status/status.go +++ b/metricbeat/module/apache/status/status.go @@ -2,11 +2,9 @@ package status import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -45,33 +43,23 @@ func init() { // MetricSet for fetching Apache HTTPD server status. type MetricSet struct { mb.BaseMetricSet - client *http.Client // HTTP client that is reused across requests. + http *helper.HTTP } // New creates new instance of MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &MetricSet{ - BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + base, + helper.NewHTTP(base), }, nil } -// Fetch makes an HTTP request to fetch status metrics from the mod_status -// endpoint. +// Fetch makes an HTTP request to fetch status metrics from the mod_status endpoint. func (m *MetricSet) Fetch() (common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - resp, err := m.client.Do(req) + scanner, err := m.http.FetchScanner() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + return nil, err } - return eventMapping(resp.Body, m.Host()), nil + return eventMapping(scanner, m.Host()), nil } diff --git a/metricbeat/module/couchbase/bucket/bucket.go b/metricbeat/module/couchbase/bucket/bucket.go index 6d95b315948..e0ac9d4af50 100644 --- a/metricbeat/module/couchbase/bucket/bucket.go +++ b/metricbeat/module/couchbase/bucket/bucket.go @@ -1,11 +1,9 @@ package bucket import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -31,29 +29,18 @@ func init() { } // MetricSet type defines all fields of the MetricSet -// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with -// additional entries. These variables can be used to persist data or configuration between -// multiple fetch calls. type MetricSet struct { mb.BaseMetricSet - client *http.Client + http *helper.HTTP } // New create a new instance of the MetricSet -// Part of new is also setting up the configuration by processing additional -// configuration entries if needed. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { logp.Warn("EXPERIMENTAL: The couchbase bucket metricset is experimental") - config := struct{}{} - - if err := base.Module().UnpackConfig(&config); err != nil { - return nil, err - } - return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), }, nil } @@ -61,23 +48,9 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // It returns the event which is then forward to the output. In case of an error, a // descriptive error must be returned. func (m *MetricSet) Fetch() ([]common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - - resp, err := m.client.Do(req) - + content, err := m.http.FetchContent() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Error Connecting to Couchbase %d: %s", resp.StatusCode, resp.Status) + return nil, err } - - return eventsMapping(resp.Body), nil - + return eventsMapping(content), nil } diff --git a/metricbeat/module/couchbase/bucket/data.go b/metricbeat/module/couchbase/bucket/data.go index e026d0b59df..b2b1f1920a3 100644 --- a/metricbeat/module/couchbase/bucket/data.go +++ b/metricbeat/module/couchbase/bucket/data.go @@ -2,7 +2,6 @@ package bucket import ( "encoding/json" - "io" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" @@ -30,10 +29,10 @@ type Buckets []struct { BasicStats BucketBasicStats `json:"basicStats"` } -func eventsMapping(body io.Reader) []common.MapStr { +func eventsMapping(content []byte) []common.MapStr { var d Buckets - err := json.NewDecoder(body).Decode(&d) + err := json.Unmarshal(content, &d) if err != nil { logp.Err("Error: ", err) } diff --git a/metricbeat/module/couchbase/cluster/cluster.go b/metricbeat/module/couchbase/cluster/cluster.go index f26e0420a39..9de12620531 100644 --- a/metricbeat/module/couchbase/cluster/cluster.go +++ b/metricbeat/module/couchbase/cluster/cluster.go @@ -1,11 +1,9 @@ package cluster import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -31,29 +29,18 @@ func init() { } // MetricSet type defines all fields of the MetricSet -// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with -// additional entries. These variables can be used to persist data or configuration between -// multiple fetch calls. type MetricSet struct { mb.BaseMetricSet - client *http.Client + http *helper.HTTP } // New create a new instance of the MetricSet -// Part of new is also setting up the configuration by processing additional -// configuration entries if needed. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { logp.Warn("EXPERIMENTAL: The couchbase cluster metricset is experimental") - config := struct{}{} - - if err := base.Module().UnpackConfig(&config); err != nil { - return nil, err - } - return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), }, nil } @@ -61,22 +48,9 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // It returns the event which is then forward to the output. In case of an error, a // descriptive error must be returned. func (m *MetricSet) Fetch() (common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - - resp, err := m.client.Do(req) - + content, err := m.http.FetchContent() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Error Connecting to Couchbase %d: %s", resp.StatusCode, resp.Status) + return nil, err } - - return eventMapping(resp.Body), nil + return eventMapping(content), nil } diff --git a/metricbeat/module/couchbase/cluster/data.go b/metricbeat/module/couchbase/cluster/data.go index 9d885e7f8c9..a9d3e0dc6a8 100644 --- a/metricbeat/module/couchbase/cluster/data.go +++ b/metricbeat/module/couchbase/cluster/data.go @@ -2,7 +2,6 @@ package cluster import ( "encoding/json" - "io" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" @@ -41,10 +40,10 @@ type Data struct { MaxBucketCount int64 `json:"maxBucketCount"` } -func eventMapping(body io.Reader) common.MapStr { +func eventMapping(content []byte) common.MapStr { var d Data - err := json.NewDecoder(body).Decode(&d) + err := json.Unmarshal(content, &d) if err != nil { logp.Err("Error: ", err) } diff --git a/metricbeat/module/couchbase/node/data.go b/metricbeat/module/couchbase/node/data.go index bd27bbb49c8..c1d34e165ce 100644 --- a/metricbeat/module/couchbase/node/data.go +++ b/metricbeat/module/couchbase/node/data.go @@ -2,11 +2,11 @@ package node import ( "encoding/json" - "io" + + "strconv" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" - "strconv" ) type NodeSystemStats struct { @@ -56,10 +56,10 @@ type Data struct { Nodes []Node `json:"nodes"` } -func eventsMapping(body io.Reader) []common.MapStr { +func eventsMapping(content []byte) []common.MapStr { var d Data - err := json.NewDecoder(body).Decode(&d) + err := json.Unmarshal(content, &d) if err != nil { logp.Err("Error: ", err) } diff --git a/metricbeat/module/couchbase/node/node.go b/metricbeat/module/couchbase/node/node.go index b88d2ba0bed..fcef19ffc34 100644 --- a/metricbeat/module/couchbase/node/node.go +++ b/metricbeat/module/couchbase/node/node.go @@ -1,11 +1,9 @@ package node import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -31,29 +29,18 @@ func init() { } // MetricSet type defines all fields of the MetricSet -// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with -// additional entries. These variables can be used to persist data or configuration between -// multiple fetch calls. type MetricSet struct { mb.BaseMetricSet - client *http.Client + http *helper.HTTP } // New create a new instance of the MetricSet -// Part of new is also setting up the configuration by processing additional -// configuration entries if needed. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { logp.Warn("EXPERIMENTAL: The couchbase node metricset is experimental") - config := struct{}{} - - if err := base.Module().UnpackConfig(&config); err != nil { - return nil, err - } - return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), }, nil } @@ -61,22 +48,10 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // It returns the event which is then forward to the output. In case of an error, a // descriptive error must be returned. func (m *MetricSet) Fetch() ([]common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - - resp, err := m.client.Do(req) - + content, err := m.http.FetchContent() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Error Connecting to Couchbase %d: %s", resp.StatusCode, resp.Status) + return nil, err } - return eventsMapping(resp.Body), nil + return eventsMapping(content), nil } diff --git a/metricbeat/module/nginx/stubstatus/data.go b/metricbeat/module/nginx/stubstatus/data.go index f30ad0f9fc4..7f4ffd92f0b 100755 --- a/metricbeat/module/nginx/stubstatus/data.go +++ b/metricbeat/module/nginx/stubstatus/data.go @@ -3,7 +3,6 @@ package stubstatus import ( "bufio" "fmt" - "io" "regexp" "strconv" @@ -17,7 +16,7 @@ var ( ) // Map body to MapStr -func eventMapping(m *MetricSet, body io.ReadCloser, hostname string, metricset string) (common.MapStr, error) { +func eventMapping(scanner *bufio.Scanner, m *MetricSet) (common.MapStr, error) { // Nginx stub status sample: // Active connections: 1 // server accepts handled requests @@ -35,8 +34,6 @@ func eventMapping(m *MetricSet, body io.ReadCloser, hostname string, metricset s waiting int ) - scanner := bufio.NewScanner(body) - // Parse active connections. scanner.Scan() matches := activeRe.FindStringSubmatch(scanner.Text()) @@ -79,8 +76,7 @@ func eventMapping(m *MetricSet, body io.ReadCloser, hostname string, metricset s waiting, _ = strconv.Atoi(matches[3]) event := common.MapStr{ - "hostname": hostname, - + "hostname": m.Host(), "active": active, "accepts": accepts, "handled": handled, diff --git a/metricbeat/module/nginx/stubstatus/stubstatus.go b/metricbeat/module/nginx/stubstatus/stubstatus.go index 97764379403..3bf30b4efb6 100755 --- a/metricbeat/module/nginx/stubstatus/stubstatus.go +++ b/metricbeat/module/nginx/stubstatus/stubstatus.go @@ -2,11 +2,8 @@ package stubstatus import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -21,8 +18,6 @@ const ( ) var ( - debugf = logp.MakeDebug("nginx-status") - hostParser = parse.URLHostParserBuilder{ DefaultScheme: defaultScheme, PathConfigKey: "server_status_path", @@ -39,33 +34,24 @@ func init() { // MetricSet for fetching Nginx stub status. type MetricSet struct { mb.BaseMetricSet - client *http.Client // HTTP client that is reused across requests. - previousNumRequests int // Total number of requests as returned in the previous fetch. + http *helper.HTTP + previousNumRequests int // Total number of requests as returned in the previous fetch. } // New creates new instance of MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), }, nil } // Fetch makes an HTTP request to fetch status metrics from the stubstatus endpoint. func (m *MetricSet) Fetch() (common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - resp, err := m.client.Do(req) + scanner, err := m.http.FetchScanner() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + return nil, err } - return eventMapping(m, resp.Body, m.Host(), m.Name()) + return eventMapping(scanner, m) } diff --git a/metricbeat/module/prometheus/collector/collector.go b/metricbeat/module/prometheus/collector/collector.go index ac7cc8e3786..44866382c57 100644 --- a/metricbeat/module/prometheus/collector/collector.go +++ b/metricbeat/module/prometheus/collector/collector.go @@ -1,12 +1,9 @@ package collector import ( - "bufio" - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -17,8 +14,6 @@ const ( ) var ( - debugf = logp.MakeDebug("prometheus-collector") - hostParser = parse.URLHostParserBuilder{ DefaultScheme: defaultScheme, DefaultPath: defaultPath, @@ -34,7 +29,7 @@ func init() { type MetricSet struct { mb.BaseMetricSet - client *http.Client + http *helper.HTTP namespace string } @@ -51,29 +46,18 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), namespace: config.Namespace, }, nil } func (m *MetricSet) Fetch() ([]common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - resp, err := m.client.Do(req) + scanner, err := m.http.FetchScanner() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + return nil, err } - eventList := map[string]common.MapStr{} - scanner := bufio.NewScanner(resp.Body) // Iterate through all events to gather data for scanner.Scan() { diff --git a/metricbeat/module/prometheus/stats/stats.go b/metricbeat/module/prometheus/stats/stats.go index 1429f6c76ba..661eae1df8b 100644 --- a/metricbeat/module/prometheus/stats/stats.go +++ b/metricbeat/module/prometheus/stats/stats.go @@ -1,13 +1,11 @@ package stats import ( - "bufio" - "fmt" - "net/http" "strings" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -18,8 +16,6 @@ const ( ) var ( - debugf = logp.MakeDebug("prometheus-stats") - hostParser = parse.URLHostParserBuilder{ DefaultScheme: defaultScheme, DefaultPath: defaultPath, @@ -34,7 +30,7 @@ func init() { type MetricSet struct { mb.BaseMetricSet - client *http.Client + http *helper.HTTP } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { @@ -42,27 +38,16 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return &MetricSet{ BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + http: helper.NewHTTP(base), }, nil } func (m *MetricSet) Fetch() (common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - if m.HostData().User != "" || m.HostData().Password != "" { - req.SetBasicAuth(m.HostData().User, m.HostData().Password) - } - resp, err := m.client.Do(req) + scanner, err := m.http.FetchScanner() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) + return nil, err } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) - } - - scanner := bufio.NewScanner(resp.Body) entries := map[string]interface{}{} From c6c5e5ced970b9bde7ded9649d83bcf0b45b470b Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Fri, 20 Jan 2017 19:39:21 +0100 Subject: [PATCH 04/78] Load Filebeat modules pipelines on -setup (#3394) * Load Filebeat modules pipelines on -setup This adds the `-setup` CLI flag, which, for now, makes Filebeat load the pipelines at startup. In case Elasticsearch is not available when Filebeat is started with `-setup`, Filebeat will exit with an error. This also exposes an Elasticsearch client from the output. * Use an interface instead of the ES client --- filebeat/beater/filebeat.go | 28 ++- filebeat/filebeat.py | 171 ------------------ filebeat/fileset/fileset.go | 27 ++- filebeat/fileset/fileset_test.go | 17 +- filebeat/fileset/modules.go | 33 ++++ filebeat/fileset/modules_integration_test.go | 58 ++++++ filebeat/fileset/modules_test.go | 2 + .../system/config/filebeat_modules.yml.j2 | 6 + filebeat/tests/system/test_modules.py | 38 ++-- libbeat/outputs/elasticsearch/api.go | 2 +- libbeat/outputs/elasticsearch/api_test.go | 53 ------ libbeat/outputs/elasticsearch/client.go | 8 +- .../elasticsearch/client_integration_test.go | 6 +- libbeat/outputs/elasticsearch/output.go | 86 +++++++++ libbeat/outputs/elasticsearch/testing.go | 58 ++++++ 15 files changed, 344 insertions(+), 249 deletions(-) delete mode 100755 filebeat/filebeat.py create mode 100644 filebeat/fileset/modules_integration_test.go create mode 100644 filebeat/tests/system/config/filebeat_modules.yml.j2 create mode 100644 libbeat/outputs/elasticsearch/testing.go diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index ce0647b4568..43cd2635c26 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -9,6 +9,7 @@ import ( "github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/outputs/elasticsearch" cfg "github.com/elastic/beats/filebeat/config" "github.com/elastic/beats/filebeat/crawler" @@ -18,7 +19,10 @@ import ( "github.com/elastic/beats/filebeat/spooler" ) -var once = flag.Bool("once", false, "Run filebeat only once until all harvesters reach EOF") +var ( + once = flag.Bool("once", false, "Run filebeat only once until all harvesters reach EOF") + setup = flag.Bool("setup", false, "Run the setup phase for the modules") +) // Filebeat is a beater object. Contains all objects needed to run the beat type Filebeat struct { @@ -67,11 +71,33 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) { return fb, nil } +// Setup is called on user request (the -setup flag) to do the initial Beat setup. +func (fb *Filebeat) Setup(b *beat.Beat) error { + esConfig := b.Config.Output["elasticsearch"] + if esConfig == nil || !esConfig.Enabled() { + return fmt.Errorf("Setup requested but the Elasticsearch output is not configured/enabled") + } + esClient, err := elasticsearch.NewConnectedClient(esConfig) + if err != nil { + return fmt.Errorf("Error creating ES client: %v", err) + } + defer esClient.Close() + + return fb.moduleRegistry.Setup(esClient) +} + // Run allows the beater to be run as a beat. func (fb *Filebeat) Run(b *beat.Beat) error { var err error config := fb.config + if *setup { + err = fb.Setup(b) + if err != nil { + return err + } + } + waitFinished := newSignalWait() waitEvents := newSignalWait() diff --git a/filebeat/filebeat.py b/filebeat/filebeat.py deleted file mode 100755 index e398c471074..00000000000 --- a/filebeat/filebeat.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -import argparse -import sys -import os -import yaml -import requests -import tempfile -import subprocess -import socket -from jinja2 import Template - - -def main(): - parser = argparse.ArgumentParser( - description="PROTOTYPE: start filebeat with a module configuration") - parser.add_argument("--modules", default="", - help="From branch") - parser.add_argument("--es", default="http://localhost:9200", - help="Elasticsearch URL") - parser.add_argument("--index", default=None, - help="Elasticsearch index") - parser.add_argument("--registry", default=None, - help="Registry file to use") - parser.add_argument("-M", nargs="*", type=str, default=None, - help="Variables overrides. e.g. path=/test") - parser.add_argument("--once", action="store_true", - help="Run filebeat with the -once flag") - - args = parser.parse_args() - print args - - # changing directory because we use paths relative to the binary - os.chdir(os.path.dirname(sys.argv[0])) - - modules = args.modules.split(",") - if len(modules) == 0: - print("You need to specify at least a module") - sys.exit(1) - - # load_dashboards(args) - load_datasets(args, modules) - - -def load_dashboards(args): - cmd = ["../libbeat/dashboards/import_dashboards", - "-dir", "_meta/kibana", - "-es", args.es] - subprocess.Popen(cmd).wait() - - -def load_datasets(args, modules): - for module in modules: - path = os.path.join("module", module) - if not os.path.isdir(path): - print("Module {} not found".format(module)) - sys.exit(1) - print("Found module {} in {}".format(module, path)) - - filesets = [name for name in os.listdir(path) if - os.path.isfile(os.path.join(path, name, "manifest.yml"))] - - print("Found filesets: {}".format(filesets)) - - for fileset in filesets: - load_fileset(args, module, fileset, - os.path.join(path, fileset)) - - run_filebeat(args) - - -def load_fileset(args, module, fileset, path): - manifest = yaml.load(file(os.path.join(path, "manifest.yml"), "r")) - var = evaluate_vars(args, manifest["var"], module, fileset) - var["beat"] = dict(module=module, fileset=fileset, path=path, args=args) - print("Evaluated variables: {}".format(var)) - - load_pipeline(var, manifest["ingest_pipeline"]) - - -def evaluate_vars(args, var_in, module, fileset): - var = { - "builtin": get_builtin_vars() - } - for vals in var_in: - name = vals["name"] - var[name] = vals["default"] - if sys.platform == "darwin" and "os.darwin" in vals: - var[name] = vals["os.darwin"] - elif sys.platform == "windows" and "os.windows" in vals: - var[name] = vals["os.windows"] - - if isinstance(var[name], basestring): - var[name] = apply_template(var[name], var) - elif isinstance(var[name], list): - # only supports array of strings atm - var[name] = [apply_template(x, var) for x in var[name]] - - return var - - -def apply_template(tpl, var): - tpl = tpl.replace("{{.", "{{") # Go templates - return Template(tpl).render(var) - - -def get_builtin_vars(): - host = socket.gethostname() - hostname, _, domain = host.partition(".") - # separate the domain - return { - "hostname": hostname, - "domain": domain - } - - -def load_pipeline(var, pipeline): - path = os.path.join(var["beat"]["path"], apply_template(pipeline, var)) - print("Loading ingest pipeline: {}".format(path)) - var["beat"]["pipeline_id"] = var["beat"]["module"] + '-' + var["beat"]["fileset"] + \ - '-' + os.path.splitext(os.path.basename(path))[0] - print("Pipeline id: {}".format(var["beat"]["pipeline_id"])) - - with open(path, "r") as f: - contents = f.read() - - r = requests.put("{}/_ingest/pipeline/{}" - .format(var["beat"]["args"].es, - var["beat"]["pipeline_id"]), - data=contents) - if r.status_code >= 300: - print("Error posting pipeline: {}".format(r.text)) - sys.exit(1) - - -def run_filebeat(args): - cfg_template = """ -output.elasticsearch.hosts: ["{{es}}"] -output.elasticsearch.pipeline: "%{[fields.pipeline_id]}" -""" - if args.index: - cfg_template += "\noutput.elasticsearch.index: {}".format(args.index) - - if args.once: - cfg_template += "\nfilebeat.idle_timeout: 0.5s" - - if args.registry: - cfg_template += "\nfilebeat.registry_file: {}".format(args.registry) - - fd, fname = tempfile.mkstemp(suffix=".yml", prefix="filebeat-", - text=True) - with open(fname, "w") as cfgfile: - cfgfile.write(Template(cfg_template).render( - dict(es=args.es))) - print("Wrote configuration file: {}".format(cfgfile.name)) - os.close(fd) - - cmd = ["./filebeat.test", "-systemTest", - "-modules", args.modules, - "-e", "-c", cfgfile.name, "-d", "*"] - for override in args.M: - cmd.extend(["-M", override]) - if args.once: - cmd.extend(["-M", "*.*.prospector.close_eof=true"]) - cmd.append("-once") - print("Starting filebeat: " + " ".join(cmd)) - - subprocess.Popen(cmd).wait() - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/filebeat/fileset/fileset.go b/filebeat/fileset/fileset.go index def198ef86a..0473f269981 100644 --- a/filebeat/fileset/fileset.go +++ b/filebeat/fileset/fileset.go @@ -7,6 +7,7 @@ package fileset import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "os" @@ -242,7 +243,31 @@ func (fs *Fileset) getPipelineID() (string, error) { return "", fmt.Errorf("Error expanding vars on the ingest pipeline path: %v", err) } - return fmt.Sprintf("%s-%s-%s", fs.mcfg.Module, fs.name, removeExt(filepath.Base(path))), nil + return formatPipelineID(fs.mcfg.Module, fs.name, path), nil +} + +func (fs *Fileset) GetPipeline() (pipelineID string, content map[string]interface{}, err error) { + path, err := applyTemplate(fs.vars, fs.manifest.IngestPipeline) + if err != nil { + return "", nil, fmt.Errorf("Error expanding vars on the ingest pipeline path: %v", err) + } + + f, err := os.Open(filepath.Join(fs.modulePath, fs.name, path)) + if err != nil { + return "", nil, fmt.Errorf("Error reading pipeline file %s: %v", path, err) + } + + dec := json.NewDecoder(f) + err = dec.Decode(&content) + if err != nil { + return "", nil, fmt.Errorf("Error JSON decoding the pipeline file: %s: %v", path, err) + } + return formatPipelineID(fs.mcfg.Module, fs.name, path), content, nil +} + +// formatPipelineID generates the ID to be used for the pipeline ID in Elasticsearch +func formatPipelineID(module, fileset, path string) string { + return fmt.Sprintf("%s-%s-%s", module, fileset, removeExt(filepath.Base(path))) } // removeExt returns the file name without the extension. If no dot is found, diff --git a/filebeat/fileset/fileset_test.go b/filebeat/fileset/fileset_test.go index 6baa7a02142..b65f04d03d2 100644 --- a/filebeat/fileset/fileset_test.go +++ b/filebeat/fileset/fileset_test.go @@ -1,3 +1,5 @@ +// +build !integration + package fileset import ( @@ -176,7 +178,18 @@ func TestGetProspectorConfigNginxOverrides(t *testing.T) { assert.True(t, cfg.HasField("paths")) assert.True(t, cfg.HasField("exclude_files")) assert.True(t, cfg.HasField("close_eof")) - pipeline_id := fs.vars["beat"].(map[string]interface{})["pipeline_id"] - assert.Equal(t, "nginx-access-with_plugins", pipeline_id) + pipelineID := fs.vars["beat"].(map[string]interface{})["pipeline_id"] + assert.Equal(t, "nginx-access-with_plugins", pipelineID) + +} +func TestGetPipelineNginx(t *testing.T) { + fs := getModuleForTesting(t, "nginx", "access") + assert.NoError(t, fs.Read()) + + pipelineID, content, err := fs.GetPipeline() + assert.NoError(t, err) + assert.Equal(t, "nginx-access-with_plugins", pipelineID) + assert.Contains(t, content, "description") + assert.Contains(t, content, "processors") } diff --git a/filebeat/fileset/modules.go b/filebeat/fileset/modules.go index 67adfb8eed6..d1dfa705504 100644 --- a/filebeat/fileset/modules.go +++ b/filebeat/fileset/modules.go @@ -240,3 +240,36 @@ func (reg *ModuleRegistry) GetProspectorConfigs() ([]*common.Config, error) { } return result, nil } + +// PipelineLoader is a subset of the Elasticsearch client API capable of loading +// the pipelines. +type PipelineLoader interface { + LoadJSON(path string, json map[string]interface{}) error +} + +// Setup is called on -setup and loads the pipelines for each configured fileset. +func (reg *ModuleRegistry) Setup(esClient PipelineLoader) error { + for module, filesets := range reg.registry { + for name, fileset := range filesets { + pipelineID, content, err := fileset.GetPipeline() + if err != nil { + return fmt.Errorf("Error getting pipeline for fileset %s/%s: %v", module, name, err) + } + err = loadPipeline(esClient, pipelineID, content) + if err != nil { + return fmt.Errorf("Error loading pipeline for fileset %s/%s: %v", module, name, err) + } + } + } + return nil +} + +func loadPipeline(esClient PipelineLoader, pipelineID string, content map[string]interface{}) error { + path := "/_ingest/pipeline/" + pipelineID + err := esClient.LoadJSON(path, content) + if err != nil { + return fmt.Errorf("couldn't load template: %v", err) + } + logp.Info("Elasticsearch pipeline with ID '%s' loaded", pipelineID) + return nil +} diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go new file mode 100644 index 00000000000..2a6167ea802 --- /dev/null +++ b/filebeat/fileset/modules_integration_test.go @@ -0,0 +1,58 @@ +// +build integration + +package fileset + +import ( + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/outputs/elasticsearch" + "github.com/stretchr/testify/assert" +) + +func TestLoadPipeline(t *testing.T) { + client := elasticsearch.GetTestingElasticsearch() + client.Request("DELETE", "/_ingest/pipeline/my-pipeline-id", "", nil, nil) + + content := map[string]interface{}{ + "description": "describe pipeline", + "processors": []map[string]interface{}{ + { + "set": map[string]interface{}{ + "field": "foo", + "value": "bar", + }, + }, + }, + } + + err := loadPipeline(client, "my-pipeline-id", content) + assert.NoError(t, err) + + status, _, _ := client.Request("GET", "/_ingest/pipeline/my-pipeline-id", "", nil, nil) + assert.Equal(t, 200, status) +} + +func TestSetupNginx(t *testing.T) { + client := elasticsearch.GetTestingElasticsearch() + client.Request("DELETE", "/_ingest/pipeline/nginx-access-with_plugins", "", nil, nil) + client.Request("DELETE", "/_ingest/pipeline/nginx-error-pipeline", "", nil, nil) + + modulesPath, err := filepath.Abs("../module") + assert.NoError(t, err) + + configs := []ModuleConfig{ + ModuleConfig{Module: "nginx"}, + } + + reg, err := newModuleRegistry(modulesPath, configs, nil) + assert.NoError(t, err) + + err = reg.Setup(client) + assert.NoError(t, err) + + status, _, _ := client.Request("GET", "/_ingest/pipeline/nginx-access-with_plugins", "", nil, nil) + assert.Equal(t, 200, status) + status, _, _ = client.Request("GET", "/_ingest/pipeline/nginx-error-pipeline", "", nil, nil) + assert.Equal(t, 200, status) +} diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index cd3e2794015..b44a93d427e 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -1,3 +1,5 @@ +// +build !integration + package fileset import ( diff --git a/filebeat/tests/system/config/filebeat_modules.yml.j2 b/filebeat/tests/system/config/filebeat_modules.yml.j2 new file mode 100644 index 00000000000..87e2937e35c --- /dev/null +++ b/filebeat/tests/system/config/filebeat_modules.yml.j2 @@ -0,0 +1,6 @@ +filebeat.idle_timeout: 0.5s +filebeat.registry_file: {{ beat.working_dir + '/' }}{{ registryFile|default("registry")}} + +output.elasticsearch.hosts: ["{{ elasticsearch_url }}"] +output.elasticsearch.index: {{ index_name }} +output.elasticsearch.pipeline: "%{[fields.pipeline_id]}" diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 5451659ec7c..845b6dff02c 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -21,7 +21,9 @@ def init(self): "/../../../../module") self.filebeat = os.path.abspath(self.working_dir + - "/../../../../filebeat.py") + "/../../../../filebeat.test") + + self.index_name = "test-filebeat-modules" @unittest.skipIf(not INTEGRATION_TESTS or os.getenv("TESTING_ENVIRONMENT") == "2x", @@ -34,6 +36,14 @@ def test_modules(self): else: modules = os.listdir(self.modules_path) + # generate a minimal configuration + cfgfile = os.path.join(self.working_dir, "filebeat.yml") + self.render_config_template( + template="filebeat_modules.yml.j2", + output=cfgfile, + index_name=self.index_name, + elasticsearch_url=self.elasticsearch_url) + for module in modules: path = os.path.join(self.modules_path, module) filesets = [name for name in os.listdir(path) if @@ -47,28 +57,28 @@ def test_modules(self): self.run_on_file( module=module, fileset=fileset, - test_file=test_file) + test_file=test_file, + cfgfile=cfgfile) - def run_on_file(self, module, fileset, test_file): + def run_on_file(self, module, fileset, test_file, cfgfile): print("Testing {}/{} on {}".format(module, fileset, test_file)) - index_name = "test-filebeat-modules" try: - self.es.indices.delete(index=index_name) + self.es.indices.delete(index=self.index_name) except: pass cmd = [ - self.filebeat, - "--once", - "--modules={}".format(module), + self.filebeat, "-systemTest", + "-e", "-d", "*", "-once", "-setup", + "-c", cfgfile, + "-modules={}".format(module), "-M", "{module}.{fileset}.var.paths=[{test_file}]".format( module=module, fileset=fileset, test_file=test_file), - "--es", self.elasticsearch_url, - "--index", index_name, - "--registry", self.working_dir + "/registry" + "-M", "*.*.prospector.close_eof=true", ] output = open(os.path.join(self.working_dir, "output.log"), "ab") + output.write(" ".join(cmd) + "\n") subprocess.Popen(cmd, stdin=None, stdout=output, @@ -76,10 +86,10 @@ def run_on_file(self, module, fileset, test_file): bufsize=0).wait() # Make sure index exists - self.wait_until(lambda: self.es.indices.exists(index_name)) + self.wait_until(lambda: self.es.indices.exists(self.index_name)) - self.es.indices.refresh(index=index_name) - res = self.es.search(index=index_name, + self.es.indices.refresh(index=self.index_name) + res = self.es.search(index=self.index_name, body={"query": {"match_all": {}}}) objects = [o["_source"] for o in res["hits"]["hits"]] assert len(objects) > 0 diff --git a/libbeat/outputs/elasticsearch/api.go b/libbeat/outputs/elasticsearch/api.go index 97687570b73..f38687a853a 100644 --- a/libbeat/outputs/elasticsearch/api.go +++ b/libbeat/outputs/elasticsearch/api.go @@ -192,5 +192,5 @@ func (es *Connection) apiCall( if err != nil { return 0, nil, err } - return es.request(method, path, pipeline, params, body) + return es.Request(method, path, pipeline, params, body) } diff --git a/libbeat/outputs/elasticsearch/api_test.go b/libbeat/outputs/elasticsearch/api_test.go index 10d04d21104..2e57abebc3b 100644 --- a/libbeat/outputs/elasticsearch/api_test.go +++ b/libbeat/outputs/elasticsearch/api_test.go @@ -2,49 +2,11 @@ package elasticsearch import ( - "os" "testing" - "time" - "github.com/elastic/beats/libbeat/outputs/outil" "github.com/stretchr/testify/assert" ) -const ElasticsearchDefaultHost = "localhost" -const ElasticsearchDefaultPort = "9200" - -func GetEsPort() string { - port := os.Getenv("ES_PORT") - - if len(port) == 0 { - port = ElasticsearchDefaultPort - } - return port -} - -// Returns -func GetEsHost() string { - - host := os.Getenv("ES_HOST") - - if len(host) == 0 { - host = ElasticsearchDefaultHost - } - - return host -} - -func GetTestingElasticsearch() *Client { - var address = "http://" + GetEsHost() + ":" + GetEsPort() - username := os.Getenv("ES_USER") - pass := os.Getenv("ES_PASS") - client := newTestClientAuth(address, username, pass) - - // Load version number - client.Connect(3 * time.Second) - return client -} - func GetValidQueryResult() QueryResult { result := QueryResult{ Ok: true, @@ -172,18 +134,3 @@ func TestReadSearchResult_invalid(t *testing.T) { func newTestClient(url string) *Client { return newTestClientAuth(url, "", "") } - -func newTestClientAuth(url, user, pass string) *Client { - client, err := NewClient(ClientSettings{ - URL: url, - Index: outil.MakeSelector(), - Username: user, - Password: pass, - Timeout: 60 * time.Second, - CompressionLevel: 3, - }, nil) - if err != nil { - panic(err) - } - return client -} diff --git a/libbeat/outputs/elasticsearch/client.go b/libbeat/outputs/elasticsearch/client.go index 649bc2db05a..2b2322af9a1 100644 --- a/libbeat/outputs/elasticsearch/client.go +++ b/libbeat/outputs/elasticsearch/client.go @@ -27,6 +27,7 @@ type Client struct { index outil.Selector pipeline *outil.Selector params map[string]string + timeout time.Duration // buffered bulk requests bulkRequ *bulkRequest @@ -173,6 +174,7 @@ func NewClient( index: s.Index, pipeline: pipeline, params: params, + timeout: s.Timeout, bulkRequ: bulkRequ, @@ -584,7 +586,7 @@ func (client *Client) LoadTemplate(templateName string, template map[string]inte } func (client *Client) LoadJSON(path string, json map[string]interface{}) error { - status, _, err := client.request("PUT", path, "", nil, json) + status, _, err := client.Request("PUT", path, "", nil, json) if err != nil { return fmt.Errorf("couldn't load json. Error: %s", err) } @@ -599,7 +601,7 @@ func (client *Client) LoadJSON(path string, json map[string]interface{}) error { // and only if Elasticsearch returns with HTTP status code 200. func (client *Client) CheckTemplate(templateName string) bool { - status, _, _ := client.request("HEAD", "/_template/"+templateName, "", nil, nil) + status, _, _ := client.Request("HEAD", "/_template/"+templateName, "", nil, nil) if status != 200 { return false @@ -657,7 +659,7 @@ func (conn *Connection) Close() error { return nil } -func (conn *Connection) request( +func (conn *Connection) Request( method, path string, pipeline string, params map[string]string, diff --git a/libbeat/outputs/elasticsearch/client_integration_test.go b/libbeat/outputs/elasticsearch/client_integration_test.go index d7857203d46..eedc4272906 100644 --- a/libbeat/outputs/elasticsearch/client_integration_test.go +++ b/libbeat/outputs/elasticsearch/client_integration_test.go @@ -62,7 +62,7 @@ func TestLoadTemplate(t *testing.T) { assert.True(t, client.CheckTemplate(templateName)) // Delete template again to clean up - client.request("DELETE", "/_template/"+templateName, "", nil, nil) + client.Request("DELETE", "/_template/"+templateName, "", nil, nil) // Make sure it was removed assert.False(t, client.CheckTemplate(templateName)) @@ -134,7 +134,7 @@ func TestLoadBeatsTemplate(t *testing.T) { assert.True(t, client.CheckTemplate(templateName)) // Delete template again to clean up - client.request("DELETE", "/_template/"+templateName, "", nil, nil) + client.Request("DELETE", "/_template/"+templateName, "", nil, nil) // Make sure it was removed assert.False(t, client.CheckTemplate(templateName)) @@ -152,7 +152,7 @@ func TestOutputLoadTemplate(t *testing.T) { } // delete template if it exists - client.request("DELETE", "/_template/libbeat", "", nil, nil) + client.Request("DELETE", "/_template/libbeat", "", nil, nil) // Make sure template is not yet there assert.False(t, client.CheckTemplate("libbeat")) diff --git a/libbeat/outputs/elasticsearch/output.go b/libbeat/outputs/elasticsearch/output.go index a2948ca022f..4ab3b03ca15 100644 --- a/libbeat/outputs/elasticsearch/output.go +++ b/libbeat/outputs/elasticsearch/output.go @@ -72,6 +72,92 @@ func New(beatName string, cfg *common.Config, topologyExpire int) (outputs.Outpu return output, nil } +// NewConnectedClient creates a new Elasticsearch client based on the given config. +// It uses the NewElasticsearchClients to create a list of clients then returns +// the first from the list that successfully connects. +func NewConnectedClient(cfg *common.Config) (*Client, error) { + clients, err := NewElasticsearchClients(cfg) + if err != nil { + return nil, err + } + + for _, client := range clients { + err = client.Connect(client.timeout) + if err != nil { + logp.Err("Error connecting to Elasticsearch: %s", client.Connection.URL) + continue + } + return &client, nil + } + return nil, fmt.Errorf("Couldn't connect to any of the configured Elasticsearch hosts") +} + +// NewElasticsearchClients returns a list of Elasticsearch clients based on the given +// configuration. It accepts the same configuration parameters as the output, +// except for the output specific configuration options (index, pipeline, +// template) .If multiple hosts are defined in the configuration, a client is returned +// for each of them. +func NewElasticsearchClients(cfg *common.Config) ([]Client, error) { + + hosts, err := modeutil.ReadHostList(cfg) + if err != nil { + return nil, err + } + + config := defaultConfig + if err := cfg.Unpack(&config); err != nil { + return nil, err + } + + tlsConfig, err := outputs.LoadTLSConfig(config.TLS) + if err != nil { + return nil, err + } + + var proxyURL *url.URL + if config.ProxyURL != "" { + proxyURL, err = parseProxyURL(config.ProxyURL) + if err != nil { + return nil, err + } + + logp.Info("Using proxy URL: %s", proxyURL) + } + + params := config.Params + if len(params) == 0 { + params = nil + } + + clients := []Client{} + for _, host := range hosts { + esURL, err := getURL(config.Protocol, config.Path, host) + if err != nil { + logp.Err("Invalid host param set: %s, Error: %v", host, err) + return nil, err + } + + client, err := NewClient(ClientSettings{ + URL: esURL, + Proxy: proxyURL, + TLS: tlsConfig, + Username: config.Username, + Password: config.Password, + Parameters: params, + Timeout: config.Timeout, + CompressionLevel: config.CompressionLevel, + }, nil) + if err != nil { + return clients, err + } + clients = append(clients, *client) + } + if len(clients) == 0 { + return clients, fmt.Errorf("No hosts defined in the Elasticsearch output") + } + return clients, nil +} + func (out *elasticsearchOutput) init( cfg *common.Config, topologyExpire int, diff --git a/libbeat/outputs/elasticsearch/testing.go b/libbeat/outputs/elasticsearch/testing.go new file mode 100644 index 00000000000..ecdab119174 --- /dev/null +++ b/libbeat/outputs/elasticsearch/testing.go @@ -0,0 +1,58 @@ +package elasticsearch + +import ( + "os" + "time" + + "github.com/elastic/beats/libbeat/outputs/outil" +) + +const ElasticsearchDefaultHost = "localhost" +const ElasticsearchDefaultPort = "9200" + +func GetEsPort() string { + port := os.Getenv("ES_PORT") + + if len(port) == 0 { + port = ElasticsearchDefaultPort + } + return port +} + +// Returns +func GetEsHost() string { + + host := os.Getenv("ES_HOST") + + if len(host) == 0 { + host = ElasticsearchDefaultHost + } + + return host +} + +func GetTestingElasticsearch() *Client { + var address = "http://" + GetEsHost() + ":" + GetEsPort() + username := os.Getenv("ES_USER") + pass := os.Getenv("ES_PASS") + client := newTestClientAuth(address, username, pass) + + // Load version number + client.Connect(3 * time.Second) + return client +} + +func newTestClientAuth(url, user, pass string) *Client { + client, err := NewClient(ClientSettings{ + URL: url, + Index: outil.MakeSelector(), + Username: user, + Password: pass, + Timeout: 60 * time.Second, + CompressionLevel: 3, + }, nil) + if err != nil { + panic(err) + } + return client +} From 59fa08b775bd7029346807b7456b1b714744d1ec Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 23 Jan 2017 11:22:27 +0100 Subject: [PATCH 05/78] Add connbeat to the list (#3437) --- libbeat/docs/communitybeats.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/libbeat/docs/communitybeats.asciidoc b/libbeat/docs/communitybeats.asciidoc index 49d412ecace..83f86f117c4 100644 --- a/libbeat/docs/communitybeats.asciidoc +++ b/libbeat/docs/communitybeats.asciidoc @@ -14,6 +14,7 @@ https://github.com/goomzee/burrowbeat[burrowbeat]:: Monitors Kafka consumer lag https://github.com/goomzee/cassandrabeat[cassandrabeat]:: Uses Cassandra's nodetool cfstats utility to monitor Cassandra database nodes and lag. https://github.com/hartfordfive/cloudflarebeat[cloudflarebeat]:: Indexes log entries from the Cloudflare Enterprise Log Share API. https://github.com/aidan-/cloudtrailbeat[cloudtrailbeat]:: Reads events from Amazon Web Services' https://aws.amazon.com/cloudtrail/[CloudTrail]. +https://github.com/raboof/connbeat[connbeat]:: Exposes metadata about TCP connections https://github.com/Pravoru/consulbeat[consulbeat]:: Reads services health checks from consul and pushes them to elastic. https://github.com/Ingensi/dockbeat[dockbeat]:: Reads Docker container statistics and indexes them in Elasticsearch. From 4f4154099a2eb84cf97db43dd57972c053891c59 Mon Sep 17 00:00:00 2001 From: Ben Gadbois Date: Mon, 23 Jan 2017 03:34:50 -0800 Subject: [PATCH 06/78] Fix small spelling mistakes (#3434) --- Makefile | 2 +- Vagrantfile | 2 +- dev-tools/README.md | 2 +- filebeat/harvester/log_test.go | 2 +- filebeat/tests/open-file-handlers/docker-compose.yml | 2 +- filebeat/tests/system/test_registrar.py | 4 ++-- heartbeat/monitors/active/icmp/loop.go | 2 +- libbeat/logp/file_rotator_test.go | 4 ++-- libbeat/scripts/generate_template.py | 2 +- metricbeat/module/couchbase/bucket/bucket_test.go | 2 +- metricbeat/module/couchbase/cluster/cluster_test.go | 2 +- metricbeat/module/couchbase/node/node_test.go | 2 +- packetbeat/SUPPORT_PROTOCOL.md | 2 +- packetbeat/_meta/beat.full.yml | 2 +- packetbeat/_meta/fields.yml | 4 ++-- packetbeat/docs/fields.asciidoc | 4 ++-- packetbeat/flows/util.go | 2 +- packetbeat/flows/worker.go | 2 +- packetbeat/packetbeat.full.yml | 2 +- packetbeat/protos/cassandra/internal/gocql/frame.go | 4 ++-- packetbeat/protos/memcache/_meta/fields.yml | 4 ++-- packetbeat/sniffer/sniffer.go | 2 +- packetbeat/tests/system/files/ThriftTest.thrift | 10 +++++----- 23 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index a7e83b173db..ffc933f0db2 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ clean-vendor: .PHONY: check check: $(foreach var,$(PROJECTS),$(MAKE) -C $(var) check || exit 1;) - # Validate that all updates were commited + # Validate that all updates were committed $(MAKE) update git update-index --refresh git diff-index --exit-code HEAD -- diff --git a/Vagrantfile b/Vagrantfile index d13d52a08b6..fec24ca7d1e 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -9,7 +9,7 @@ # This box is used as a Windows development and testing environment for Beats. # # Usage and Features: -# - Two users exist: Administartor and Vagrant. Both have the password: vagrant +# - Two users exist: Administrator and Vagrant. Both have the password: vagrant # - Use 'vagrant ssh' to open a Windows command prompt. # - Use 'vagrant rdp' to open a Windows Remote Deskop session. Mac users must # install the Microsoft Remote Desktop Client from the App Store. diff --git a/dev-tools/README.md b/dev-tools/README.md index 5516f3222b4..b418ba6efb4 100644 --- a/dev-tools/README.md +++ b/dev-tools/README.md @@ -44,7 +44,7 @@ pip install -r requirements.txt This creates the environment that contains all the python packages required to run the `export_dashboards.py` script. Thus, for the next runs you just need -to enable the enviroment: +to enable the environment: ``` . env/bin/activate diff --git a/filebeat/harvester/log_test.go b/filebeat/harvester/log_test.go index b441561bebf..910d611ac76 100644 --- a/filebeat/harvester/log_test.go +++ b/filebeat/harvester/log_test.go @@ -132,7 +132,7 @@ func TestInitRegexp(t *testing.T) { // readLine reads a full line into buffer and returns it. // In case of partial lines, readLine does return an error and an empty string -// This could potentialy be improved / replaced by https://github.com/elastic/beats/libbeat/tree/master/common/streambuf +// This could potentially be improved / replaced by https://github.com/elastic/beats/libbeat/tree/master/common/streambuf func readLine(reader reader.Reader) (time.Time, string, int, common.MapStr, error) { message, err := reader.Next() diff --git a/filebeat/tests/open-file-handlers/docker-compose.yml b/filebeat/tests/open-file-handlers/docker-compose.yml index f70a3423a87..661e13a3116 100644 --- a/filebeat/tests/open-file-handlers/docker-compose.yml +++ b/filebeat/tests/open-file-handlers/docker-compose.yml @@ -19,7 +19,7 @@ logs: - filebeat -#addtional: +#additional: # image: debian # links: # - filebeat diff --git a/filebeat/tests/system/test_registrar.py b/filebeat/tests/system/test_registrar.py index 61b0e6510fb..7a57485435c 100644 --- a/filebeat/tests/system/test_registrar.py +++ b/filebeat/tests/system/test_registrar.py @@ -1009,7 +1009,7 @@ def test_restart_state_reset(self): assert len(data) == 1 assert data[0]["ttl"] > 0 - # No config file which does not match the exisitng state + # No config file which does not match the existing state self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/test2.log", clean_inactive="10s", @@ -1127,7 +1127,7 @@ def test_restart_state_reset_ttl_with_space(self): assert len(data) == 1 assert data[0]["ttl"] == 20 * 1000 * 1000 * 1000 - # new config file whith other clean_inactive + # new config file with other clean_inactive self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/test file.log", clean_inactive="40s", diff --git a/heartbeat/monitors/active/icmp/loop.go b/heartbeat/monitors/active/icmp/loop.go index 4319fe2174f..e1ef007c961 100644 --- a/heartbeat/monitors/active/icmp/loop.go +++ b/heartbeat/monitors/active/icmp/loop.go @@ -324,7 +324,7 @@ func createListener(name, network string) *icmp.PacketConn { conn, err := icmp.ListenPacket(network, "") // XXX: need to check for conn == nil, as 'err != nil' seems always to be - // true, even if error value itself is `nil`. Checking for conn supresses + // true, even if error value itself is `nil`. Checking for conn suppresses // missleading log message. if conn == nil && err != nil { logp.Info("%v ICMP not supported: %v", name, err) diff --git a/libbeat/logp/file_rotator_test.go b/libbeat/logp/file_rotator_test.go index 01c9ed0f1a0..6df204bbbfe 100644 --- a/libbeat/logp/file_rotator_test.go +++ b/libbeat/logp/file_rotator_test.go @@ -24,7 +24,7 @@ func Test_Rotator(t *testing.T) { return } - Debug("rotator", "Direcotry: %s", dir) + Debug("rotator", "Directory: %s", dir) rotateeverybytes := uint64(1000) keepfiles := 3 @@ -118,7 +118,7 @@ func Test_Rotator_By_Bytes(t *testing.T) { return } - Debug("rotator", "Direcotry: %s", dir) + Debug("rotator", "Directory: %s", dir) rotateeverybytes := uint64(100) keepfiles := 3 diff --git a/libbeat/scripts/generate_template.py b/libbeat/scripts/generate_template.py index 5efcaf17cac..20bb99845e2 100644 --- a/libbeat/scripts/generate_template.py +++ b/libbeat/scripts/generate_template.py @@ -309,7 +309,7 @@ def fill_field_properties(args, field, defaults, path): dynamic_templates.extend(dynamic) else: - raise ValueError("Unkown type found: " + field.get("type")) + raise ValueError("Unknown type found: " + field.get("type")) return properties, dynamic_templates diff --git a/metricbeat/module/couchbase/bucket/bucket_test.go b/metricbeat/module/couchbase/bucket/bucket_test.go index 0d849d63ecf..bbdcce3f7da 100644 --- a/metricbeat/module/couchbase/bucket/bucket_test.go +++ b/metricbeat/module/couchbase/bucket/bucket_test.go @@ -21,7 +21,7 @@ func TestFetchEventContents(t *testing.T) { response, err := ioutil.ReadFile(absPath + "/sample_response.json") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - w.Header().Set("Content-Type", "appication/json;") + w.Header().Set("Content-Type", "application/json;") w.Write([]byte(response)) })) defer server.Close() diff --git a/metricbeat/module/couchbase/cluster/cluster_test.go b/metricbeat/module/couchbase/cluster/cluster_test.go index 97c202d44ab..de1bed4f28e 100644 --- a/metricbeat/module/couchbase/cluster/cluster_test.go +++ b/metricbeat/module/couchbase/cluster/cluster_test.go @@ -21,7 +21,7 @@ func TestFetchEventContents(t *testing.T) { response, err := ioutil.ReadFile(absPath + "/sample_response.json") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - w.Header().Set("Content-Type", "appication/json;") + w.Header().Set("Content-Type", "application/json;") w.Write([]byte(response)) })) defer server.Close() diff --git a/metricbeat/module/couchbase/node/node_test.go b/metricbeat/module/couchbase/node/node_test.go index f49b71f0686..22f7ba874d4 100644 --- a/metricbeat/module/couchbase/node/node_test.go +++ b/metricbeat/module/couchbase/node/node_test.go @@ -21,7 +21,7 @@ func TestFetchEventContents(t *testing.T) { response, err := ioutil.ReadFile(absPath + "/sample_response.json") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - w.Header().Set("Content-Type", "appication/json;") + w.Header().Set("Content-Type", "application/json;") w.Write([]byte(response)) })) defer server.Close() diff --git a/packetbeat/SUPPORT_PROTOCOL.md b/packetbeat/SUPPORT_PROTOCOL.md index 8f58405d3b4..8fc54284c63 100644 --- a/packetbeat/SUPPORT_PROTOCOL.md +++ b/packetbeat/SUPPORT_PROTOCOL.md @@ -1,6 +1,6 @@ # Support a new protocol -This is a simple guide, written while implementing support for mongodb protocol in order to make life easier for following developpers. +This is a simple guide, written while implementing support for mongodb protocol in order to make life easier for following developers. # Have a look at the code diff --git a/packetbeat/_meta/beat.full.yml b/packetbeat/_meta/beat.full.yml index 32f421e8b32..bba989ae9c7 100644 --- a/packetbeat/_meta/beat.full.yml +++ b/packetbeat/_meta/beat.full.yml @@ -82,7 +82,7 @@ packetbeat.protocols.amqp: # Default: false #parse_arguments: false - # Hide all methods relative to connection negociation between server and + # Hide all methods relative to connection negotiation between server and # client. # Default: true #hide_connection_information: true diff --git a/packetbeat/_meta/fields.yml b/packetbeat/_meta/fields.yml index f2d98b41dc0..808f30b5641 100644 --- a/packetbeat/_meta/fields.yml +++ b/packetbeat/_meta/fields.yml @@ -1387,13 +1387,13 @@ type: long format: bytes description: > - The byte count of the values being transfered. + The byte count of the values being transferred. - name: response.bytes type: long format: bytes description: > - The byte count of the values being transfered. + The byte count of the values being transferred. - name: request.delta type: long diff --git a/packetbeat/docs/fields.asciidoc b/packetbeat/docs/fields.asciidoc index a468e4fa6a0..1eec67e1935 100644 --- a/packetbeat/docs/fields.asciidoc +++ b/packetbeat/docs/fields.asciidoc @@ -2064,7 +2064,7 @@ type: long format: bytes -The byte count of the values being transfered. +The byte count of the values being transferred. [float] @@ -2074,7 +2074,7 @@ type: long format: bytes -The byte count of the values being transfered. +The byte count of the values being transferred. [float] diff --git a/packetbeat/flows/util.go b/packetbeat/flows/util.go index 95663ff640d..9840b520849 100644 --- a/packetbeat/flows/util.go +++ b/packetbeat/flows/util.go @@ -66,7 +66,7 @@ func (w *worker) tick(t *time.Ticker) bool { } } -func (w *worker) periodicaly(tick time.Duration, fn func() error) { +func (w *worker) periodically(tick time.Duration, fn func() error) { defer debugf("stop periodic loop") ticker := time.NewTicker(tick) diff --git a/packetbeat/flows/worker.go b/packetbeat/flows/worker.go index 0063bb1a811..189ddd0c549 100644 --- a/packetbeat/flows/worker.go +++ b/packetbeat/flows/worker.go @@ -96,7 +96,7 @@ func makeWorker( nPeriod := ticksPeriod reportPeriodically := ticksPeriod > 0 debugf("start flows worker loop") - w.periodicaly(tickDuration, func() error { + w.periodically(tickDuration, func() error { nTimeout-- nPeriod-- debugf("worker tick, nTimeout=%v, nPeriod=%v", nTimeout, nPeriod) diff --git a/packetbeat/packetbeat.full.yml b/packetbeat/packetbeat.full.yml index 27663ba82d5..fe992c0bd18 100644 --- a/packetbeat/packetbeat.full.yml +++ b/packetbeat/packetbeat.full.yml @@ -82,7 +82,7 @@ packetbeat.protocols.amqp: # Default: false #parse_arguments: false - # Hide all methods relative to connection negociation between server and + # Hide all methods relative to connection negotiation between server and # client. # Default: true #hide_connection_information: true diff --git a/packetbeat/protos/cassandra/internal/gocql/frame.go b/packetbeat/protos/cassandra/internal/gocql/frame.go index c14bc903815..3115d78fa25 100644 --- a/packetbeat/protos/cassandra/internal/gocql/frame.go +++ b/packetbeat/protos/cassandra/internal/gocql/frame.go @@ -201,8 +201,8 @@ func (f *Framer) ReadFrame() (data map[string]interface{}, err error) { if f.Header.Flags&flagCompress == flagCompress { //decompress data and switch to use bytearray decoder if f.compres == nil { - logp.Err("hit compress flag, but comprossor was not set") - panic(errors.New("hit compress flag, but comprossor was not set")) + logp.Err("hit compress flag, but compressor was not set") + panic(errors.New("hit compress flag, but compressor was not set")) } decoder := &ByteArrayDecoder{} diff --git a/packetbeat/protos/memcache/_meta/fields.yml b/packetbeat/protos/memcache/_meta/fields.yml index 1d42fe9e231..3d5fae104eb 100644 --- a/packetbeat/protos/memcache/_meta/fields.yml +++ b/packetbeat/protos/memcache/_meta/fields.yml @@ -137,13 +137,13 @@ type: long format: bytes description: > - The byte count of the values being transfered. + The byte count of the values being transferred. - name: response.bytes type: long format: bytes description: > - The byte count of the values being transfered. + The byte count of the values being transferred. - name: request.delta type: long diff --git a/packetbeat/sniffer/sniffer.go b/packetbeat/sniffer/sniffer.go index 71f3fb8f7dc..7bbf6b58362 100644 --- a/packetbeat/sniffer/sniffer.go +++ b/packetbeat/sniffer/sniffer.go @@ -76,7 +76,7 @@ func deviceNameFromIndex(index int, devices []string) (string, error) { // ListDevicesNames returns the list of adapters available for sniffing on // this computer. If the withDescription parameter is set to true, a human // readable version of the adapter name is added. If the withIP parameter -// is set to true, IP address of the adatper is added. +// is set to true, IP address of the adapter is added. func ListDeviceNames(withDescription bool, withIP bool) ([]string, error) { devices, err := pcap.FindAllDevs() if err != nil { diff --git a/packetbeat/tests/system/files/ThriftTest.thrift b/packetbeat/tests/system/files/ThriftTest.thrift index ef23a5eac62..ab03ba91226 100644 --- a/packetbeat/tests/system/files/ThriftTest.thrift +++ b/packetbeat/tests/system/files/ThriftTest.thrift @@ -169,7 +169,7 @@ service ThriftTest double testDouble(1: double thing), /** - * Prints 'testStruct("{%s}")' where thing has been formatted into a string of comma seperated values + * Prints 'testStruct("{%s}")' where thing has been formatted into a string of comma separated values * @param Xtruct thing - the Xtruct to print * @return Xtruct - returns the Xtruct 'thing' */ @@ -184,7 +184,7 @@ service ThriftTest /** * Prints 'testMap("{%s")' where thing has been formatted into a string of 'key => value' pairs - * seperated by commas and new lines + * separated by commas and new lines * @param map thing - the map to print * @return map - returns the map 'thing' */ @@ -192,7 +192,7 @@ service ThriftTest /** * Prints 'testStringMap("{%s}")' where thing has been formatted into a string of 'key => value' pairs - * seperated by commas and new lines + * separated by commas and new lines * @param map thing - the map to print * @return map - returns the map 'thing' */ @@ -200,7 +200,7 @@ service ThriftTest /** * Prints 'testSet("{%s}")' where thing has been formatted into a string of values - * seperated by commas and new lines + * separated by commas and new lines * @param set thing - the set to print * @return set - returns the set 'thing' */ @@ -208,7 +208,7 @@ service ThriftTest /** * Prints 'testList("{%s}")' where thing has been formatted into a string of values - * seperated by commas and new lines + * separated by commas and new lines * @param list thing - the list to print * @return list - returns the list 'thing' */ From 240bbd276122a2fd94add0de3c58259611ed3647 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Mon, 23 Jan 2017 14:51:21 +0100 Subject: [PATCH 07/78] Implement prospector reloading (#3362) This PR allows to dynamically reload prospectors. It works the same way as module reloading in metricbeat. **Refactoring** * LoadStates was separated from NewProspector. The reason is that after New only the ID is needed and setting up states requires more calculations. So this can be done in a second step when all the validations are done. * Only allow to start a prospector when all states are set to Finished. If not, LoadStates returns an error. This is to prevent a prospector starting before a harvester finished with a file. The prospector will be picked up again during the next reloading phase. * Extract ReloadConfig to libbeat **Limitations** This implementation currently has the some limitations. This are not new in filebeat but require more care as configurations change more often. * Two prospectors on one file: It is possible, that two prospectors pick up one file because they defined overlapping patterns. This can have the consequence that two harvesters on the same file are running which can lead to duplicates and unpredictable behaviour. The risk is minimized in that a prospector does not start as long as a state it takes care of is not finished. But it can still happen that a Finished state is picked up but it also managed by an other prospector. The user must ensure no prospector paths overlap. This problem can potentially be solved in the future with a global harvester registry. **Notes** * In a later PR, more refactoring and unification of the reloading should happen. --- filebeat/beater/filebeat.go | 8 +- filebeat/config/config.go | 17 +- filebeat/crawler/crawler.go | 56 ++++-- filebeat/crawler/crawler_test.go | 18 -- .../docs/reference/configuration.asciidoc | 1 + .../configuration/filebeat-options.asciidoc | 4 +- .../reload-configuration.asciidoc | 41 ++++ filebeat/prospector/prospector.go | 13 +- filebeat/prospector/prospector_log.go | 10 +- .../prospector/prospector_log_other_test.go | 7 +- filebeat/prospector/prospector_stdin.go | 7 +- filebeat/prospector/prospector_test.go | 2 +- filebeat/prospector/registry.go | 46 +++++ filebeat/prospector/reloader.go | 177 ++++++++++++++++++ filebeat/tests/system/config/filebeat.yml.j2 | 6 + filebeat/tests/system/test_reload.py | 140 ++++++++++++++ libbeat/cfgfile/glob_watcher.go | 8 +- libbeat/cfgfile/glob_watcher_test.go | 2 +- .../config.go => libbeat/cfgfile/reload.go | 20 +- libbeat/common/config.go | 6 +- metricbeat/mb/module/reload.go | 7 +- 21 files changed, 513 insertions(+), 83 deletions(-) delete mode 100644 filebeat/crawler/crawler_test.go create mode 100644 filebeat/docs/reference/configuration/reload-configuration.asciidoc create mode 100644 filebeat/prospector/registry.go create mode 100644 filebeat/prospector/reloader.go create mode 100644 filebeat/tests/system/test_reload.py rename metricbeat/mb/module/config.go => libbeat/cfgfile/reload.go (55%) diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index 43cd2635c26..3f3bb7c8882 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -59,10 +59,14 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) { // Add prospectors created by the modules config.Prospectors = append(config.Prospectors, moduleProspectors...) - if len(config.Prospectors) == 0 { + if !config.ProspectorReload.Enabled() && len(config.Prospectors) == 0 { return nil, errors.New("No prospectors defined. What files do you want me to watch?") } + if *once && config.ProspectorReload.Enabled() { + return nil, errors.New("prospector reloading and -once cannot be used together.") + } + fb := &Filebeat{ done: make(chan struct{}), config: &config, @@ -170,7 +174,7 @@ func (fb *Filebeat) Run(b *beat.Beat) error { spooler.Stop() }() - err = crawler.Start(registrar) + err = crawler.Start(registrar, config.ProspectorReload) if err != nil { return err } diff --git a/filebeat/config/config.go b/filebeat/config/config.go index d15f2e4c4bb..744c8094faa 100644 --- a/filebeat/config/config.go +++ b/filebeat/config/config.go @@ -18,14 +18,15 @@ const ( ) type Config struct { - Prospectors []*common.Config `config:"prospectors"` - SpoolSize uint64 `config:"spool_size" validate:"min=1"` - PublishAsync bool `config:"publish_async"` - IdleTimeout time.Duration `config:"idle_timeout" validate:"nonzero,min=0s"` - RegistryFile string `config:"registry_file"` - ConfigDir string `config:"config_dir"` - ShutdownTimeout time.Duration `config:"shutdown_timeout"` - Modules []*common.Config `config:"modules"` + Prospectors []*common.Config `config:"prospectors"` + SpoolSize uint64 `config:"spool_size" validate:"min=1"` + PublishAsync bool `config:"publish_async"` + IdleTimeout time.Duration `config:"idle_timeout" validate:"nonzero,min=0s"` + RegistryFile string `config:"registry_file"` + ConfigDir string `config:"config_dir"` + ShutdownTimeout time.Duration `config:"shutdown_timeout"` + Modules []*common.Config `config:"modules"` + ProspectorReload *common.Config `config:"reload.prospectors"` } var ( diff --git a/filebeat/crawler/crawler.go b/filebeat/crawler/crawler.go index 573f89a96ef..d5a388214e6 100644 --- a/filebeat/crawler/crawler.go +++ b/filebeat/crawler/crawler.go @@ -16,15 +16,12 @@ type Crawler struct { prospectorConfigs []*common.Config out prospector.Outlet wg sync.WaitGroup + reloader *prospector.ProspectorReloader once bool } func New(out prospector.Outlet, prospectorConfigs []*common.Config, once bool) (*Crawler, error) { - if len(prospectorConfigs) == 0 { - return nil, fmt.Errorf("No prospectors defined. You must have at least one prospector defined in the config file.") - } - return &Crawler{ out: out, prospectors: map[uint64]*prospector.Prospector{}, @@ -33,7 +30,7 @@ func New(out prospector.Outlet, prospectorConfigs []*common.Config, once bool) ( }, nil } -func (c *Crawler) Start(r *registrar.Registrar) error { +func (c *Crawler) Start(r *registrar.Registrar, reloaderConfig *common.Config) error { logp.Info("Loading Prospectors: %v", len(c.prospectorConfigs)) @@ -45,6 +42,15 @@ func (c *Crawler) Start(r *registrar.Registrar) error { } } + if reloaderConfig.Enabled() { + logp.Warn("EXPERIMENTAL feature dynamic configuration reloading is enabled.") + + c.reloader = prospector.NewProspectorReloader(reloaderConfig, c.out, r) + go func() { + c.reloader.Run() + }() + } + logp.Info("Loading and starting Prospectors completed. Enabled prospectors: %v", len(c.prospectors)) return nil @@ -54,25 +60,30 @@ func (c *Crawler) startProspector(config *common.Config, states []file.State) er if !config.Enabled() { return nil } - prospector, err := prospector.NewProspector(config, states, c.out) + p, err := prospector.NewProspector(config, c.out) if err != nil { return fmt.Errorf("Error in initing prospector: %s", err) } - prospector.Once = c.once + p.Once = c.once + + if _, ok := c.prospectors[p.ID]; ok { + return fmt.Errorf("Prospector with same ID already exists: %v", p.ID) + } - if _, ok := c.prospectors[prospector.ID]; ok { - return fmt.Errorf("Prospector with same ID already exists: %v", prospector.ID) + err = p.LoadStates(states) + if err != nil { + return fmt.Errorf("error loading states for propsector %v: %v", p.ID, err) } - c.prospectors[prospector.ID] = prospector + c.prospectors[p.ID] = p c.wg.Add(1) go func() { - logp.Debug("crawler", "Starting prospector: %v", prospector.ID) - defer logp.Debug("crawler", "Prospector stopped: %v", prospector.ID) + logp.Debug("crawler", "Starting prospector: %v", p.ID) + defer logp.Debug("crawler", "Prospector stopped: %v", p.ID) defer c.wg.Done() - prospector.Run() + p.Run() }() return nil @@ -80,18 +91,27 @@ func (c *Crawler) startProspector(config *common.Config, states []file.State) er func (c *Crawler) Stop() { logp.Info("Stopping Crawler") - stopProspector := func(p *prospector.Prospector) { - defer c.wg.Done() - p.Stop() + + asyncWaitStop := func(stop func()) { + c.wg.Add(1) + go func() { + defer c.wg.Done() + stop() + }() } logp.Info("Stopping %v prospectors", len(c.prospectors)) for _, p := range c.prospectors { // Stop prospectors in parallel - c.wg.Add(1) - go stopProspector(p) + asyncWaitStop(p.Stop) } + + if c.reloader != nil { + asyncWaitStop(c.reloader.Stop) + } + c.WaitForCompletion() + logp.Info("Crawler stopped") } diff --git a/filebeat/crawler/crawler_test.go b/filebeat/crawler/crawler_test.go deleted file mode 100644 index a72c80a0e7c..00000000000 --- a/filebeat/crawler/crawler_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build !integration - -package crawler - -import ( - "testing" - - "github.com/elastic/beats/libbeat/common" - "github.com/stretchr/testify/assert" -) - -func TestNewCrawlerNoProspectorsError(t *testing.T) { - prospectorConfigs := []*common.Config{} - - _, error := New(nil, prospectorConfigs, false) - - assert.Error(t, error) -} diff --git a/filebeat/docs/reference/configuration.asciidoc b/filebeat/docs/reference/configuration.asciidoc index 05d9ec0c05e..637b01c2587 100644 --- a/filebeat/docs/reference/configuration.asciidoc +++ b/filebeat/docs/reference/configuration.asciidoc @@ -12,6 +12,7 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> * <> +* <> * <> * <> * <> diff --git a/filebeat/docs/reference/configuration/filebeat-options.asciidoc b/filebeat/docs/reference/configuration/filebeat-options.asciidoc index 599697728f2..91bebc207d9 100644 --- a/filebeat/docs/reference/configuration/filebeat-options.asciidoc +++ b/filebeat/docs/reference/configuration/filebeat-options.asciidoc @@ -240,7 +240,7 @@ When this option is enabled, Filebeat closes a file as soon as the end of a file WARNING: Only use this option if you understand that data loss is a potential side effect. Another side effect is that multiline events might not be completely sent before the timeout expires. -When this option is enabled, Filebeat gives every harvester a predefined lifetime. Regardless of where the reader is in the file, reading will stop after the `close_timeout` period has elapsed. This option can be useful for older log files when you want to spend only a predefined amount of time on the files. While `close_timeout` will close the file after the predefined timeout, if the file is still being updated, the prospector will start a new harvester again per the defined `scan_frequency`. And the close_timeout for this harvester will start again with the countdown for the timeout. +When this option is enabled, Filebeat gives every harvester a predefined lifetime. Regardless of where the reader is in the file, reading will stop after the `close_timeout` period has elapsed. This option can be useful for older log files when you want to spend only a predefined amount of time on the files. While `close_timeout` will close the file after the predefined timeout, if the file is still being updated, the prospector will start a new harvester again per the defined `scan_frequency`. And the close_timeout for this harvester will start again with the countdown for the timeout. If you set `close_timeout` to equal `ignore_older`, the file will not be picked up if it's modified while the harvester is closed. This combination of settings normally leads to data loss, and the complete file is not sent. @@ -572,6 +572,8 @@ filebeat.shutdown_timeout: 5s pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] +include::./reload-configuration.asciidoc[] + pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] diff --git a/filebeat/docs/reference/configuration/reload-configuration.asciidoc b/filebeat/docs/reference/configuration/reload-configuration.asciidoc new file mode 100644 index 00000000000..06df9d6b0b9 --- /dev/null +++ b/filebeat/docs/reference/configuration/reload-configuration.asciidoc @@ -0,0 +1,41 @@ +[[filebeat-configuration-reloading]] +=== Reload Configuration + +experimental[] + +Reload configuration allows to dynamically reload prospector configuration files. A glob can be defined which should be watched + for prospector configuration changes. New prospectors will be started / stopped accordingly. This is especially useful in + container environments where 1 container is used to tail logs from services in other containers on the same host. + +The configuration in the main filebeat.yml config file looks as following: + +[source,yaml] +------------------------------------------------------------------------------ +filebeat.reload.prospectors: + enabled: true + path: configs/*.yml + period: 10s +------------------------------------------------------------------------------ + +A path with a glob must be defined on which files should be checked for changes. A period is set on how often +the files are checked for changes. Do not set period below 1s as the modification time of files is often stored in seconds. +Setting it below 1s will cause an unnecessary overhead. + +The configuration inside the files which are found by the glob look as following: +[source,yaml] +------------------------------------------------------------------------------ +- input_type: log + paths: + - /var/log/mysql.log + scan_frequency: 10s + +- input_type: log + paths: + - /var/log/apache.log + scan_frequency: 5s +------------------------------------------------------------------------------ + +Each file directly contains a list of prospectors. Each file can contain one or multiple prospector definitions. + +WARNING: It is critical that two running prospectors DO NOT have overlapping file paths defined. If more then one prospector +harvests the same file at the same time, it can lead to unexpected behaviour. diff --git a/filebeat/prospector/prospector.go b/filebeat/prospector/prospector.go index 7e745889216..3ce402298af 100644 --- a/filebeat/prospector/prospector.go +++ b/filebeat/prospector/prospector.go @@ -39,7 +39,7 @@ type Prospector struct { } type Prospectorer interface { - Init(states []file.State) error + LoadStates(states []file.State) error Run() } @@ -47,7 +47,7 @@ type Outlet interface { OnEvent(event *input.Event) bool } -func NewProspector(cfg *common.Config, states []file.State, outlet Outlet) (*Prospector, error) { +func NewProspector(cfg *common.Config, outlet Outlet) (*Prospector, error) { prospector := &Prospector{ cfg: cfg, config: defaultConfig, @@ -72,18 +72,13 @@ func NewProspector(cfg *common.Config, states []file.State, outlet Outlet) (*Pro return nil, err } - err = prospector.Init(states) - if err != nil { - return nil, err - } - logp.Debug("prospector", "File Configs: %v", prospector.config.Paths) return prospector, nil } // Init sets up default config for prospector -func (p *Prospector) Init(states []file.State) error { +func (p *Prospector) LoadStates(states []file.State) error { var prospectorer Prospectorer var err error @@ -101,7 +96,7 @@ func (p *Prospector) Init(states []file.State) error { return err } - err = prospectorer.Init(states) + err = prospectorer.LoadStates(states) if err != nil { return err } diff --git a/filebeat/prospector/prospector_log.go b/filebeat/prospector/prospector_log.go index 3dd49547db0..b4ae09fc7e5 100644 --- a/filebeat/prospector/prospector_log.go +++ b/filebeat/prospector/prospector_log.go @@ -2,6 +2,7 @@ package prospector import ( "expvar" + "fmt" "os" "path/filepath" "runtime" @@ -34,10 +35,10 @@ func NewProspectorLog(p *Prospector) (*ProspectorLog, error) { return prospectorer, nil } -// Init sets up the prospector +// LoadStates loads states into prospector // It goes through all states coming from the registry. Only the states which match the glob patterns of // the prospector will be loaded and updated. All other states will not be touched. -func (p *ProspectorLog) Init(states []file.State) error { +func (p *ProspectorLog) LoadStates(states []file.State) error { logp.Debug("prospector", "exclude_files: %s", p.config.ExcludeFiles) for _, state := range states { @@ -45,6 +46,11 @@ func (p *ProspectorLog) Init(states []file.State) error { if p.matchesFile(state.Source) { state.TTL = -1 + // In case a prospector is tried to be started with an unfinished state matching the glob pattern + if !state.Finished { + return fmt.Errorf("Can only start a prospector when all related states are finished: %+v", state) + } + // Update prospector states and send new states to registry err := p.Prospector.updateState(input.NewEvent(state)) if err != nil { diff --git a/filebeat/prospector/prospector_log_other_test.go b/filebeat/prospector/prospector_log_other_test.go index 2a2fb0f013f..8108968576e 100644 --- a/filebeat/prospector/prospector_log_other_test.go +++ b/filebeat/prospector/prospector_log_other_test.go @@ -139,8 +139,13 @@ func TestInit(t *testing.T) { }, } states := file.NewStates() + // Set states to finished + for i, state := range test.states { + state.Finished = true + test.states[i] = state + } states.SetStates(test.states) - err := p.Init(states.GetStates()) + err := p.LoadStates(states.GetStates()) assert.NoError(t, err) assert.Equal(t, test.count, p.Prospector.states.Count()) } diff --git a/filebeat/prospector/prospector_stdin.go b/filebeat/prospector/prospector_stdin.go index 10b671d6b73..0279edcb4f3 100644 --- a/filebeat/prospector/prospector_stdin.go +++ b/filebeat/prospector/prospector_stdin.go @@ -17,7 +17,9 @@ type ProspectorStdin struct { // This prospector contains one harvester which is reading from stdin func NewProspectorStdin(p *Prospector) (*ProspectorStdin, error) { - prospectorer := &ProspectorStdin{} + prospectorer := &ProspectorStdin{ + started: false, + } var err error @@ -29,8 +31,7 @@ func NewProspectorStdin(p *Prospector) (*ProspectorStdin, error) { return prospectorer, nil } -func (p *ProspectorStdin) Init(states []file.State) error { - p.started = false +func (p *ProspectorStdin) LoadStates(states []file.State) error { return nil } diff --git a/filebeat/prospector/prospector_test.go b/filebeat/prospector/prospector_test.go index 720e63a67b3..f4ed5460def 100644 --- a/filebeat/prospector/prospector_test.go +++ b/filebeat/prospector/prospector_test.go @@ -19,7 +19,7 @@ func TestProspectorInitInputTypeLogError(t *testing.T) { states := file.NewStates() states.SetStates([]file.State{}) - err := prospector.Init(states.GetStates()) + err := prospector.LoadStates(states.GetStates()) // Error should be returned because no path is set assert.Error(t, err) } diff --git a/filebeat/prospector/registry.go b/filebeat/prospector/registry.go new file mode 100644 index 00000000000..71ca0c82253 --- /dev/null +++ b/filebeat/prospector/registry.go @@ -0,0 +1,46 @@ +package prospector + +import "sync" + +type registry struct { + sync.Mutex + List map[uint64]*Prospector +} + +func newRegistry() *registry { + return ®istry{ + List: map[uint64]*Prospector{}, + } +} + +func (r *registry) Add(hash uint64, m *Prospector) { + r.Lock() + defer r.Unlock() + r.List[hash] = m +} + +func (r *registry) Remove(hash uint64) { + r.Lock() + defer r.Unlock() + delete(r.List, hash) +} + +func (r *registry) Has(hash uint64) bool { + r.Lock() + defer r.Unlock() + + _, ok := r.List[hash] + return ok +} + +func (r *registry) CopyList() map[uint64]*Prospector { + r.Lock() + defer r.Unlock() + + // Create a copy of the list + list := map[uint64]*Prospector{} + for k, v := range r.List { + list[k] = v + } + return list +} diff --git a/filebeat/prospector/reloader.go b/filebeat/prospector/reloader.go new file mode 100644 index 00000000000..f8f21da39d7 --- /dev/null +++ b/filebeat/prospector/reloader.go @@ -0,0 +1,177 @@ +package prospector + +import ( + "expvar" + "path/filepath" + "sync" + "time" + + "github.com/elastic/beats/filebeat/registrar" + "github.com/elastic/beats/libbeat/cfgfile" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/paths" +) + +var ( + debugr = logp.MakeDebug("filebeat.reloader") + configReloads = expvar.NewInt("filebeat.config.reloads") + prospectorStarts = expvar.NewInt("filebeat.config.prospector.dyamic.starts") + prospectorStops = expvar.NewInt("filebeat.config.prospector.dyamic.stops") + prospectorRunning = expvar.NewInt("filebeat.config.prospector.dyamic.running") +) + +type ProspectorReloader struct { + registry *registry + config cfgfile.ReloadConfig + outlet Outlet + done chan struct{} + wg sync.WaitGroup + registrar *registrar.Registrar +} + +func NewProspectorReloader(cfg *common.Config, outlet Outlet, registrar *registrar.Registrar) *ProspectorReloader { + + config := cfgfile.DefaultReloadConfig + cfg.Unpack(&config) + + return &ProspectorReloader{ + registry: newRegistry(), + config: config, + outlet: outlet, + done: make(chan struct{}), + registrar: registrar, + } +} + +func (r *ProspectorReloader) Run() { + + logp.Info("Prospector reloader started") + + r.wg.Add(1) + defer r.wg.Done() + + // Stop all running prospectors when method finishes + defer r.stopProspectors(r.registry.CopyList()) + + path := r.config.Path + if !filepath.IsAbs(path) { + path = paths.Resolve(paths.Config, path) + } + + gw := cfgfile.NewGlobWatcher(path) + + for { + select { + case <-r.done: + logp.Info("Dynamic config reloader stopped") + return + case <-time.After(r.config.Period): + + debugr("Scan for new config files") + + files, updated, err := gw.Scan() + if err != nil { + // In most cases of error, updated == false, so will continue + // to next iteration below + logp.Err("Error fetching new config files: %v", err) + } + + // no file changes + if !updated { + continue + } + + configReloads.Add(1) + + // Load all config objects + configs := []*common.Config{} + for _, file := range files { + c, err := cfgfile.LoadList(file) + if err != nil { + logp.Err("Error loading config: %s", err) + continue + } + + configs = append(configs, c...) + } + + debugr("Number of prospectors configs created: %v", len(configs)) + + var startList []*Prospector + stopList := r.registry.CopyList() + + for _, c := range configs { + + // Only add prospectors to startlist which are enabled + if !c.Enabled() { + continue + } + + p, err := NewProspector(c, r.outlet) + if err != nil { + logp.Err("Error creating prospector: %s", err) + continue + } + + debugr("Remove prospector from stoplist: %v", p.ID) + delete(stopList, p.ID) + + // As prospector already exist, it must be removed from the stop list and not started + if !r.registry.Has(p.ID) { + debugr("Add prospector to startlist: %v", p.ID) + startList = append(startList, p) + continue + } + } + + r.stopProspectors(stopList) + r.startProspectors(startList) + } + } +} + +func (r *ProspectorReloader) startProspectors(prospectors []*Prospector) { + for _, p := range prospectors { + err := p.LoadStates(r.registrar.GetStates()) + if err != nil { + logp.Err("Error loading states for prospector %v: %v", p.ID, err) + continue + } + r.registry.Add(p.ID, p) + go func(pr *Prospector) { + prospectorStarts.Add(1) + prospectorRunning.Add(1) + defer func() { + r.registry.Remove(pr.ID) + logp.Info("Prospector stopped: %v", pr.ID) + }() + pr.Run() + }(p) + } + +} + +func (r *ProspectorReloader) stopProspectors(prospectors map[uint64]*Prospector) { + wg := sync.WaitGroup{} + for _, p := range prospectors { + wg.Add(1) + go func(pr *Prospector) { + defer wg.Done() + logp.Debug("reload", "stopping prospector: %v", pr.ID) + pr.Stop() + prospectorStops.Add(1) + prospectorRunning.Add(-1) + }(p) + } + wg.Wait() +} + +func (r *ProspectorReloader) Stop() { + close(r.done) + // Wait until reloading finished + r.wg.Wait() + + // Stop all prospectors + r.stopProspectors(r.registry.CopyList()) +} diff --git a/filebeat/tests/system/config/filebeat.yml.j2 b/filebeat/tests/system/config/filebeat.yml.j2 index c3cc5aa2220..5b34221f828 100644 --- a/filebeat/tests/system/config/filebeat.yml.j2 +++ b/filebeat/tests/system/config/filebeat.yml.j2 @@ -78,6 +78,12 @@ filebeat.registry_file: {{ beat.working_dir + '/' }}{{ registryFile|default("reg {%endif%} filebeat.publish_async: {{publish_async}} +{% if reload -%} +filebeat.reload.prospectors: + path: {{ reload_path }} + period: 1s + enabled: true +{% endif -%} #================================ General ===================================== diff --git a/filebeat/tests/system/test_reload.py b/filebeat/tests/system/test_reload.py new file mode 100644 index 00000000000..27bf73afaef --- /dev/null +++ b/filebeat/tests/system/test_reload.py @@ -0,0 +1,140 @@ +import re +import sys +import unittest +import os +import time +from filebeat import BaseTest + + +prospectorConfigTemplate = """ +- input_type: log + paths: + - {} + scan_frequency: 1s +""" + +class Test(BaseTest): + + def test_reload(self): + """ + Test basic reload + """ + self.render_config_template( + reload=True, + reload_path=self.working_dir + "/configs/*.yml", + prospectors=False, + ) + + proc = self.start_beat() + + os.mkdir(self.working_dir + "/logs/") + logfile = self.working_dir + "/logs/test.log" + os.mkdir(self.working_dir + "/configs/") + + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write(prospectorConfigTemplate.format(self.working_dir + "/logs/*")) + + with open(logfile, 'w') as f: + f.write("Hello world\n") + + self.wait_until(lambda: self.output_lines() > 0) + proc.check_kill_and_wait() + + def test_start_stop(self): + """ + Test basic start and stop + """ + self.render_config_template( + reload=True, + reload_path=self.working_dir + "/configs/*.yml", + prospectors=False, + ) + + proc = self.start_beat() + + os.mkdir(self.working_dir + "/logs/") + logfile = self.working_dir + "/logs/test.log" + os.mkdir(self.working_dir + "/configs/") + + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write(prospectorConfigTemplate.format(self.working_dir + "/logs/*")) + + with open(logfile, 'w') as f: + f.write("Hello world\n") + + self.wait_until(lambda: self.output_lines() == 1) + + # Remove prospector + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write("") + + # Wait until prospector is stopped + self.wait_until( + lambda: self.log_contains("Prospector stopped:"), + max_timeout=15) + + with open(logfile, 'a') as f: + f.write("Hello world\n") + + # Wait to give a change to pick up the new line (it shouldn't) + time.sleep(1) + + proc.check_kill_and_wait() + + assert self.output_lines() == 1 + + def test_start_stop_replace(self): + """ + Test basic start and replace with an other prospecto + """ + self.render_config_template( + reload=True, + reload_path=self.working_dir + "/configs/*.yml", + prospectors=False, + ) + + proc = self.start_beat() + + os.mkdir(self.working_dir + "/logs/") + logfile1 = self.working_dir + "/logs/test1.log" + logfile2 = self.working_dir + "/logs/test2.log" + os.mkdir(self.working_dir + "/configs/") + first_line = "First log file" + second_line = "Second log file" + + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write(prospectorConfigTemplate.format(self.working_dir + "/logs/test1.log")) + + with open(logfile1, 'w') as f: + f.write(first_line + "\n") + + self.wait_until(lambda: self.output_lines() == 1) + + # Remove prospector + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write("") + + # Wait until prospector is stopped + self.wait_until( + lambda: self.log_contains("Prospector stopped:"), + max_timeout=15) + + with open(self.working_dir + "/configs/prospector.yml", 'w') as f: + f.write(prospectorConfigTemplate.format(self.working_dir + "/logs/test2.log")) + + # Update both log files, only 1 change should be picke dup + with open(logfile1, 'a') as f: + f.write("First log file 1\n") + with open(logfile2, 'a') as f: + f.write(second_line + "\n") + + self.wait_until(lambda: self.output_lines() == 2) + + proc.check_kill_and_wait() + + output = self.read_output() + + # Make sure the correct lines were picked up + assert output[0]["message"] == first_line + assert output[1]["message"] == second_line + assert self.output_lines() == 2 diff --git a/libbeat/cfgfile/glob_watcher.go b/libbeat/cfgfile/glob_watcher.go index 38a328acb1a..30d6292af31 100644 --- a/libbeat/cfgfile/glob_watcher.go +++ b/libbeat/cfgfile/glob_watcher.go @@ -56,11 +56,15 @@ func (gw *GlobWatcher) Scan() ([]string, bool, error) { } // Check if one of the files was changed recently - // File modification time can be in seconds. -1 is to cover for files which + // File modification time can be in seconds. -1 + truncation is to cover for files which // were created during this second. - if info.ModTime().After(gw.lastScan.Truncate(time.Second)) { + // If the last scan was at 09:02:15.00001 it will pick up files which were modified also 09:02:14 + // As this scan no necessarly picked up files form 09:02:14 + // TODO: How could this be improved / simplified? Behaviour was sometimes flaky. Is ModTime updated with delay? + if info.ModTime().After(gw.lastScan.Add(-1 * time.Second).Truncate(time.Second)) { updatedFiles = true } + files = append(files, f) } diff --git a/libbeat/cfgfile/glob_watcher_test.go b/libbeat/cfgfile/glob_watcher_test.go index 25405730541..65da24287f4 100644 --- a/libbeat/cfgfile/glob_watcher_test.go +++ b/libbeat/cfgfile/glob_watcher_test.go @@ -29,7 +29,7 @@ func TestGlobWatcher(t *testing.T) { assert.NoError(t, err) // Make sure not inside compensation time - time.Sleep(1 * time.Second) + time.Sleep(2 * time.Second) files, changed, err := gcd.Scan() assert.Equal(t, 2, len(files)) diff --git a/metricbeat/mb/module/config.go b/libbeat/cfgfile/reload.go similarity index 55% rename from metricbeat/mb/module/config.go rename to libbeat/cfgfile/reload.go index e81f5d73442..44f398ccec1 100644 --- a/metricbeat/mb/module/config.go +++ b/libbeat/cfgfile/reload.go @@ -1,19 +1,17 @@ -package module +package cfgfile import "time" -// ReloaderConfig contains config options for the module Reloader. -type ReloaderConfig struct { - // If path is a relative path, it is relative to the ${path.config} - Path string `config:"path"` - Period time.Duration `config:"period"` - Enabled bool `config:"enabled"` -} - var ( - // DefaultReloaderConfig contains the default config options. - DefaultReloaderConfig = ReloaderConfig{ + DefaultReloadConfig = ReloadConfig{ Period: 10 * time.Second, Enabled: false, } ) + +type ReloadConfig struct { + // If path is a relative path, it is relative to the ${path.config} + Path string `config:"path"` + Period time.Duration `config:"period"` + Enabled bool `config:"enabled"` +} diff --git a/libbeat/common/config.go b/libbeat/common/config.go index 5355ef2408e..593471acbdf 100644 --- a/libbeat/common/config.go +++ b/libbeat/common/config.go @@ -78,7 +78,7 @@ func MergeConfigs(cfgs ...*Config) (*Config, error) { func NewConfigWithYAML(in []byte, source string) (*Config, error) { opts := append( []ucfg.Option{ - ucfg.MetaData(ucfg.Meta{source}), + ucfg.MetaData(ucfg.Meta{Source: source}), }, configOpts..., ) @@ -94,7 +94,7 @@ func NewFlagConfig( ) *Config { opts := append( []ucfg.Option{ - ucfg.MetaData(ucfg.Meta{"command line flag"}), + ucfg.MetaData(ucfg.Meta{Source: "command line flag"}), }, configOpts..., ) @@ -289,7 +289,7 @@ func (f *flagOverwrite) String() string { func (f *flagOverwrite) Set(v string) error { opts := append( []ucfg.Option{ - ucfg.MetaData(ucfg.Meta{"command line flag"}), + ucfg.MetaData(ucfg.Meta{Source: "command line flag"}), }, configOpts..., ) diff --git a/metricbeat/mb/module/reload.go b/metricbeat/mb/module/reload.go index 9fb0ced0203..184555ffdc6 100644 --- a/metricbeat/mb/module/reload.go +++ b/metricbeat/mb/module/reload.go @@ -9,6 +9,7 @@ import ( "github.com/elastic/beats/libbeat/cfgfile" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/paths" "github.com/elastic/beats/libbeat/publisher" "github.com/elastic/beats/metricbeat/mb" ) @@ -23,7 +24,7 @@ var ( // Reloader is used to register and reload modules type Reloader struct { registry *registry - config ReloaderConfig + config cfgfile.ReloadConfig client func() publisher.Client done chan struct{} wg sync.WaitGroup @@ -32,7 +33,7 @@ type Reloader struct { // NewReloader creates new Reloader instance for the given config func NewReloader(cfg *common.Config, p publisher.Publisher) *Reloader { - config := DefaultReloaderConfig + config := cfgfile.DefaultReloadConfig cfg.Unpack(&config) return &Reloader{ @@ -56,7 +57,7 @@ func (r *Reloader) Run() { path := r.config.Path if !filepath.IsAbs(path) { - path = filepath.Join(cfgfile.GetPathConfig(), path) + path = paths.Resolve(paths.Config, path) } gw := cfgfile.NewGlobWatcher(path) From f1b59cde8a3b4ac63f60a33ac1d73e172649861e Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Tue, 24 Jan 2017 01:10:30 -0500 Subject: [PATCH 08/78] Generate coverage report after fixing file permissions (#3445) This changes the Makefile to run coverage-report after fix-permissions. This allows coverage-report to be able to fully traverse the contents the build dir. --- libbeat/scripts/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index db1c98dc8ab..c8c6bc96a9f 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -209,13 +209,13 @@ testsuite: clean update fi \ fi - $(MAKE) benchmark-tests - $(MAKE) coverage-report - if [ $(TEST_ENVIRONMENT) = true ]; then \ $(MAKE) fix-permissions; \ fi + $(MAKE) benchmark-tests + $(MAKE) coverage-report + # Generates a coverage report from the existing coverage files .PHONY: coverage-report coverage-report: From e75197ea114e6e71eb5f57f62df85b5c1acfabdd Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 24 Jan 2017 08:52:54 +0100 Subject: [PATCH 09/78] Deploy Filebeat modules in the packages (#3436) Uses an intermediary `_meta/module.generated/` folder which is created on `make collect`. This gives us the opportunity to select which files are needed to be deployed in the package (currently everything but the `_meta` and `test` folders). The intermediary folder also allows us to avoid providing a custom `install-home` target in Filebeat. Part of #3159. --- filebeat/.gitignore | 1 + filebeat/Makefile | 8 +++++++- libbeat/scripts/Makefile | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/filebeat/.gitignore b/filebeat/.gitignore index 6af3ddf18e9..3b6a7232e74 100644 --- a/filebeat/.gitignore +++ b/filebeat/.gitignore @@ -6,4 +6,5 @@ filebeat build _meta/kibana +_meta/module.generated /tests/load/logs diff --git a/filebeat/Makefile b/filebeat/Makefile index bbd6bd94282..6ac7d0da625 100644 --- a/filebeat/Makefile +++ b/filebeat/Makefile @@ -27,10 +27,16 @@ fields: cat ${ES_BEATS}/filebeat/_meta/fields.common.yml > _meta/fields.generated.yml . ${PYTHON_ENV}/bin/activate; python ${ES_BEATS}/metricbeat/scripts/fields_collector.py >> _meta/fields.generated.yml +# Collects all modules files to be packaged in a temporary folder +.PHONY: modules +modules: + mkdir -p _meta/ + rm -rf _meta/module.generated + rsync -av module/ _meta/module.generated --exclude "_meta" --exclude "*/*/test" # Runs all collection steps and updates afterwards .PHONY: collect -collect: fields kibana +collect: fields kibana modules # Creates a new fileset. Requires the params MODULE and FILESET diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index c8c6bc96a9f..5039733f0f3 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -327,6 +327,10 @@ install-home: if [ -a ${NOTICE_FILE} ]; then \ install -m 644 ${NOTICE_FILE} ${HOME_PREFIX}/; \ fi + if [ -d _meta/module.generated ]; then \ + install -d -m 755 ${HOME_PREFIX}/module; \ + rsync -av _meta/module.generated/ ${HOME_PREFIX}/module/; \ + fi # Prepares for packaging. Builds binaries and creates homedir data .PHONY: prepare-package From c7d30dcc35c6fe958f466e8640e19788f3423ccd Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 24 Jan 2017 08:56:55 +0100 Subject: [PATCH 10/78] Change Metricbeat generator to be based on python (#3438) This removes the dependency on cookiecutter for generating a beat based on Metricbeat. Further changes: * Unify handling of beat_path in the generator scripts * Merge generator script for both beats types * Move metricbeat generator out of metricset directory as it generates a beat and metricset is only a follow up call. * Enabled metricbeat modules by default to have something to get started. --- .travis.yml | 2 +- generate/Makefile | 4 +- generate/beat.py | 105 +---------------- generate/beat/Makefile | 2 +- generate/beat/{beat}/Makefile | 2 +- generate/beat/{beat}/README.md | 4 +- generate/beat/{beat}/beater/{beat}.go.tmpl | 2 +- generate/beat/{beat}/main.go.tmpl | 2 +- generate/helper.py | 108 ++++++++++++++++++ generate/metricbeat.py | 8 ++ .../metricbeat/{metricset => }/.gitignore | 0 .../metricbeat/{metricset => }/CHANGELOG.md | 0 generate/metricbeat/{metricset => }/LICENSE | 0 generate/metricbeat/{metricset => }/Makefile | 28 ++--- generate/metricbeat/{metricset => }/README.md | 0 .../metricbeat/metricset/cookiecutter.json | 7 -- .../metricset/tests/cookiecutter.json | 7 -- .../.gitignore | 0 .../CONTRIBUTING.md | 0 .../{{cookiecutter.beat}} => {beat}}/LICENSE | 2 +- .../{{cookiecutter.beat}} => {beat}}/Makefile | 4 +- .../{{cookiecutter.beat}} => {beat}}/NOTICE | 4 +- .../README.md | 8 +- .../main.go => {beat}/main.go.tmpl} | 8 +- .../creating-beat-from-metricbeat.asciidoc | 6 +- 25 files changed, 157 insertions(+), 156 deletions(-) create mode 100644 generate/helper.py create mode 100644 generate/metricbeat.py rename generate/metricbeat/{metricset => }/.gitignore (100%) rename generate/metricbeat/{metricset => }/CHANGELOG.md (100%) rename generate/metricbeat/{metricset => }/LICENSE (100%) rename generate/metricbeat/{metricset => }/Makefile (65%) rename generate/metricbeat/{metricset => }/README.md (100%) delete mode 100644 generate/metricbeat/metricset/cookiecutter.json delete mode 100644 generate/metricbeat/metricset/tests/cookiecutter.json rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/.gitignore (100%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/CONTRIBUTING.md (100%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/LICENSE (91%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/Makefile (89%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/NOTICE (61%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}} => {beat}}/README.md (89%) rename generate/metricbeat/{metricset/{{cookiecutter.beat}}/main.go => {beat}/main.go.tmpl} (54%) diff --git a/.travis.yml b/.travis.yml index 2fe10844006..55bf7809561 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,7 +74,7 @@ matrix: # Generators - os: linux - env: TARGETS="-C generate/metricbeat/metricset test" + env: TARGETS="-C generate/metricbeat test" go: *go_version - os: linux env: TARGETS="-C generate/beat test" diff --git a/generate/Makefile b/generate/Makefile index f5e7e02a981..0e6e08f37bd 100644 --- a/generate/Makefile +++ b/generate/Makefile @@ -3,10 +3,10 @@ .PHONY: test test: $(MAKE) -C beat test - $(MAKE) -C metricbeat/metricset test + $(MAKE) -C metricbeat test # Cleans up environment .PHONY: clean clean: $(MAKE) -C beat clean - $(MAKE) -C metricbeat/metricset clean + $(MAKE) -C metricbeat clean diff --git a/generate/beat.py b/generate/beat.py index f4602fe3108..a7bffa1a4e8 100644 --- a/generate/beat.py +++ b/generate/beat.py @@ -1,107 +1,8 @@ -import os -import argparse - -# Creates a new beat based on the given parameters - -project_name = "" -github_name = "" -beat = "" -beat_path = "" -full_name = "" - -def generate_beat(): - read_input() - process_file() - -def read_input(): - """Requests input form the command line for empty variables if needed. - """ - global project_name, github_name, beat, beat_path, full_name - - if project_name == "": - project_name = raw_input("Beat Name [Examplebeat]: ") or "examplebeat" - - if github_name == "": - github_name = raw_input("Your Github Name [your-github-name]: ") or "your-github-name" - beat = project_name.lower() - - if beat_path == "": - beat_path = raw_input("Beat Path [github.com/" + github_name + "]: ") or "github.com/" + github_name - - if full_name == "": - full_name = raw_input("Firstname Lastname: ") or "Firstname Lastname" - -def process_file(): - - # Load path information - generator_path = os.path.dirname(os.path.realpath(__file__)) - go_path = os.environ['GOPATH'] - - for root, dirs, files in os.walk(generator_path + '/beat/{beat}'): - - for file in files: - - full_path = root + "/" + file - - ## load file - content = "" - with open(full_path) as f: - content = f.read() - - # process content - content = replace_variables(content) - - # Write new path - new_path = replace_variables(full_path).replace(".go.tmpl", ".go") - - # remove generator info from path - file_path = new_path.replace(generator_path + "/beat/", "") - - # New file path to write file content to - write_file = go_path + "/src/" + beat_path + "/" + file_path - - # Create parent directory if it does not exist yet - dir = os.path.dirname(write_file) - if not os.path.exists(dir): - os.makedirs(dir) - - # Write file to new location - with open(write_file, 'w') as f: - f.write(content) - -def replace_variables(content): - """Replace all template variables with the actual values - """ - return content.replace("{project_name}", project_name) \ - .replace("{github_name}", github_name) \ - .replace("{beat}", beat) \ - .replace("{Beat}", beat.capitalize()) \ - .replace("{beat_path}", beat_path) \ - .replace("{full_name}", full_name) - +import helper if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Creates a beat") - parser.add_argument("--project_name", help="Project name") - parser.add_argument("--github_name", help="Github name") - parser.add_argument("--beat_path", help="Beat path") - parser.add_argument("--full_name", help="Full name") - + parser = helper.get_parser() args = parser.parse_args() - - - if args.project_name is not None: - project_name = args.project_name - - if args.github_name is not None: - github_name = args.github_name - - if args.beat_path is not None: - beat_path = args.beat_path - - if args.full_name is not None: - full_name = args.full_name - - generate_beat() + helper.generate_beat("beat", args) diff --git a/generate/beat/Makefile b/generate/beat/Makefile index ae52948da55..3d93de44d8e 100644 --- a/generate/beat/Makefile +++ b/generate/beat/Makefile @@ -14,7 +14,7 @@ test: python-env mkdir -p ${BEAT_PATH} export GOPATH=${PWD}/build ; \ - . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/generate/beat.py --project_name=Testbeat --github_name=ruflin --beat_path=beatpath --full_name="Nicolas Ruflin" + . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/generate/beat.py --project_name=Testbeat --github_name=ruflin --beat_path=beatpath/testbeat --full_name="Nicolas Ruflin" . ${PYTHON_ENV}/bin/activate; \ export GOPATH=${PWD}/build ; \ diff --git a/generate/beat/{beat}/Makefile b/generate/beat/{beat}/Makefile index f27e1267055..6747288ed87 100644 --- a/generate/beat/{beat}/Makefile +++ b/generate/beat/{beat}/Makefile @@ -1,5 +1,5 @@ BEAT_NAME={beat} -BEAT_PATH={beat_path}/{beat} +BEAT_PATH={beat_path} BEAT_GOPATH=$(firstword $(subst :, ,${GOPATH})) BEAT_URL=https://${BEAT_PATH} SYSTEM_TESTS=false diff --git a/generate/beat/{beat}/README.md b/generate/beat/{beat}/README.md index 0963d4dc3a0..f7041e2e5c6 100644 --- a/generate/beat/{beat}/README.md +++ b/generate/beat/{beat}/README.md @@ -24,7 +24,7 @@ It will create a clean git history for each major step. Note that you can always To push {Beat} in the git repository, run the following commands: ``` -git remote set-url origin https://{beat_path}/{beat} +git remote set-url origin https://{beat_path} git push origin master ``` @@ -101,7 +101,7 @@ To clone {Beat} from the git repository, run the following commands: ``` mkdir -p ${GOPATH}/{beat_path} cd ${GOPATH}/{beat_path} -git clone https://{beat_path}/{beat} +git clone https://{beat_path} ``` diff --git a/generate/beat/{beat}/beater/{beat}.go.tmpl b/generate/beat/{beat}/beater/{beat}.go.tmpl index 6f946f5e0d3..978259ede76 100644 --- a/generate/beat/{beat}/beater/{beat}.go.tmpl +++ b/generate/beat/{beat}/beater/{beat}.go.tmpl @@ -9,7 +9,7 @@ import ( "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/libbeat/publisher" - "{beat_path}/{beat}/config" + "{beat_path}/config" ) type {Beat} struct { diff --git a/generate/beat/{beat}/main.go.tmpl b/generate/beat/{beat}/main.go.tmpl index 6919fddbb24..974a2768a1b 100644 --- a/generate/beat/{beat}/main.go.tmpl +++ b/generate/beat/{beat}/main.go.tmpl @@ -5,7 +5,7 @@ import ( "github.com/elastic/beats/libbeat/beat" - "{beat_path}/{beat}/beater" + "{beat_path}/beater" ) func main() { diff --git a/generate/helper.py b/generate/helper.py new file mode 100644 index 00000000000..70b63729443 --- /dev/null +++ b/generate/helper.py @@ -0,0 +1,108 @@ +import os +import argparse + +# Creates a new beat based on the given parameters + +project_name = "" +github_name = "" +beat = "" +beat_path = "" +full_name = "" + +def generate_beat(template_path, args): + + global project_name, github_name, beat, beat_path, full_name + + if args.project_name is not None: + project_name = args.project_name + + if args.github_name is not None: + github_name = args.github_name + + if args.beat_path is not None: + beat_path = args.beat_path + + if args.full_name is not None: + full_name = args.full_name + + read_input() + process_file(template_path) + +def read_input(): + """Requests input form the command line for empty variables if needed. + """ + global project_name, github_name, beat, beat_path, full_name + + if project_name == "": + project_name = raw_input("Beat Name [Examplebeat]: ") or "examplebeat" + + if github_name == "": + github_name = raw_input("Your Github Name [your-github-name]: ") or "your-github-name" + beat = project_name.lower() + + if beat_path == "": + beat_path = raw_input("Beat Path [github.com/" + github_name + "/" + beat + "]: ") or "github.com/" + github_name + "/" + beat + + if full_name == "": + full_name = raw_input("Firstname Lastname: ") or "Firstname Lastname" + +def process_file(template_path): + + # Load path information + generator_path = os.path.dirname(os.path.realpath(__file__)) + go_path = os.environ['GOPATH'] + + for root, dirs, files in os.walk(generator_path + '/' + template_path + '/{beat}'): + + for file in files: + + full_path = root + "/" + file + + ## load file + content = "" + with open(full_path) as f: + content = f.read() + + # process content + content = replace_variables(content) + + # Write new path + new_path = replace_variables(full_path).replace(".go.tmpl", ".go") + + # remove generator info and beat name from path + file_path = new_path.replace(generator_path + "/" + template_path + "/" + beat, "") + + # New file path to write file content to + write_file = go_path + "/src/" + beat_path + "/" + file_path + + # Create parent directory if it does not exist yet + dir = os.path.dirname(write_file) + if not os.path.exists(dir): + os.makedirs(dir) + + # Write file to new location + with open(write_file, 'w') as f: + f.write(content) + +def replace_variables(content): + """Replace all template variables with the actual values + """ + return content.replace("{project_name}", project_name) \ + .replace("{github_name}", github_name) \ + .replace("{beat}", beat) \ + .replace("{Beat}", beat.capitalize()) \ + .replace("{beat_path}", beat_path) \ + .replace("{full_name}", full_name) + + +def get_parser(): + """Creates parser to parse script params + """ + parser = argparse.ArgumentParser(description="Creates a beat") + parser.add_argument("--project_name", help="Project name") + parser.add_argument("--github_name", help="Github name") + parser.add_argument("--beat_path", help="Beat path") + parser.add_argument("--full_name", help="Full name") + + return parser + diff --git a/generate/metricbeat.py b/generate/metricbeat.py new file mode 100644 index 00000000000..022f33ad003 --- /dev/null +++ b/generate/metricbeat.py @@ -0,0 +1,8 @@ +import helper + +if __name__ == "__main__": + + parser = helper.get_parser() + args = parser.parse_args() + helper.generate_beat("metricbeat", args) + diff --git a/generate/metricbeat/metricset/.gitignore b/generate/metricbeat/.gitignore similarity index 100% rename from generate/metricbeat/metricset/.gitignore rename to generate/metricbeat/.gitignore diff --git a/generate/metricbeat/metricset/CHANGELOG.md b/generate/metricbeat/CHANGELOG.md similarity index 100% rename from generate/metricbeat/metricset/CHANGELOG.md rename to generate/metricbeat/CHANGELOG.md diff --git a/generate/metricbeat/metricset/LICENSE b/generate/metricbeat/LICENSE similarity index 100% rename from generate/metricbeat/metricset/LICENSE rename to generate/metricbeat/LICENSE diff --git a/generate/metricbeat/metricset/Makefile b/generate/metricbeat/Makefile similarity index 65% rename from generate/metricbeat/metricset/Makefile rename to generate/metricbeat/Makefile index 5dcfb3bb4da..4cbc34dca64 100644 --- a/generate/metricbeat/metricset/Makefile +++ b/generate/metricbeat/Makefile @@ -1,34 +1,34 @@ BUILD_DIR?=build PWD=$(shell pwd) PYTHON_ENV?=${BUILD_DIR}/python-env -BEAT_PATH=${BUILD_DIR}/src/beatpath/countbeat +BEAT_PATH=${BUILD_DIR}/src/beatpath/testbeat -# Runs test build for the count metricset + +# Runs test build for mock beat .PHONY: test test: python-env - # Create copy of cookiecutter for building with defaults - mkdir -p build/ - cp -r \{\{cookiecutter.beat\}\} build - cp tests/cookiecutter.json build/ - - mkdir -p ${BEAT_PATH} - . ${PYTHON_ENV}/bin/activate && cookiecutter --no-input -o build/src/beatpath -f build - # Makes sure to use current version of beats for testing mkdir -p ${BUILD_DIR}/src/github.com/elastic/beats/ - rsync -a --exclude=generate ${PWD}/../../../* ${BUILD_DIR}/src/github.com/elastic/beats/ + rsync -a --exclude=build ${PWD}/../../* ${BUILD_DIR}/src/github.com/elastic/beats/ + + mkdir -p ${BEAT_PATH} + export GOPATH=${PWD}/build ; \ + . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/generate/metricbeat.py --project_name=Testbeat --github_name=ruflin --beat_path=beatpath/testbeat --full_name="Nicolas Ruflin" . ${PYTHON_ENV}/bin/activate; \ export GOPATH=${PWD}/build ; \ export PATH=${PATH}:${PWD}/build/bin; \ - cd ${BEAT_PATH}; \ - make copy-vendor; \ + cd ${BEAT_PATH} ; \ + make copy-vendor ; \ MODULE=elastic METRICSET=test make create-metricset ; \ - make imports collect check ; \ + make check ; \ + make update ; \ + make ; \ make unit + # Tests the build process for the beat .PHONY: test-build test-build: test diff --git a/generate/metricbeat/metricset/README.md b/generate/metricbeat/README.md similarity index 100% rename from generate/metricbeat/metricset/README.md rename to generate/metricbeat/README.md diff --git a/generate/metricbeat/metricset/cookiecutter.json b/generate/metricbeat/metricset/cookiecutter.json deleted file mode 100644 index a6fcaf047bb..00000000000 --- a/generate/metricbeat/metricset/cookiecutter.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "project_name": "Examplebeat", - "github_name": "your-github-name", - "beat": "{{ cookiecutter.project_name|lower }}", - "beat_path": "github.com/{{ cookiecutter.github_name|lower }}", - "full_name": "Firstname Lastname" -} diff --git a/generate/metricbeat/metricset/tests/cookiecutter.json b/generate/metricbeat/metricset/tests/cookiecutter.json deleted file mode 100644 index 6ab985c1450..00000000000 --- a/generate/metricbeat/metricset/tests/cookiecutter.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "project_name": "Countbeat", - "github_name": "ruflin", - "beat": "{{ cookiecutter.project_name|lower }}", - "beat_path": "beatpath", - "full_name": "Nicolas Ruflin" -} diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/.gitignore b/generate/metricbeat/{beat}/.gitignore similarity index 100% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/.gitignore rename to generate/metricbeat/{beat}/.gitignore diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/CONTRIBUTING.md b/generate/metricbeat/{beat}/CONTRIBUTING.md similarity index 100% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/CONTRIBUTING.md rename to generate/metricbeat/{beat}/CONTRIBUTING.md diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/LICENSE b/generate/metricbeat/{beat}/LICENSE similarity index 91% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/LICENSE rename to generate/metricbeat/{beat}/LICENSE index acb13bbf6d4..fff2fbe40eb 100644 --- a/generate/metricbeat/metricset/{{cookiecutter.beat}}/LICENSE +++ b/generate/metricbeat/{beat}/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 {{cookiecutter.full_name}} +Copyright (c) 2016 {full_name} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/Makefile b/generate/metricbeat/{beat}/Makefile similarity index 89% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/Makefile rename to generate/metricbeat/{beat}/Makefile index 2f1aca611b0..f33a645bf11 100644 --- a/generate/metricbeat/metricset/{{cookiecutter.beat}}/Makefile +++ b/generate/metricbeat/{beat}/Makefile @@ -1,5 +1,5 @@ -BEAT_NAME={{cookiecutter.beat}} -BEAT_PATH={{cookiecutter.beat_path}}/{{cookiecutter.beat}} +BEAT_NAME={beat} +BEAT_PATH={beat_path} BEAT_URL=https://${BEAT_PATH} SYSTEM_TESTS=false TEST_ENVIRONMENT=false diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/NOTICE b/generate/metricbeat/{beat}/NOTICE similarity index 61% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/NOTICE rename to generate/metricbeat/{beat}/NOTICE index 19a8b48bdfd..3898d85d3ba 100644 --- a/generate/metricbeat/metricset/{{cookiecutter.beat}}/NOTICE +++ b/generate/metricbeat/{beat}/NOTICE @@ -1,5 +1,5 @@ -{{cookiecutter.beat}} -Copyright 2017 {{cookiecutter.full_name}} +{beat} +Copyright 2017 {full_name} This product includes software developed by The Apache Software Foundation (http://www.apache.org/). diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/README.md b/generate/metricbeat/{beat}/README.md similarity index 89% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/README.md rename to generate/metricbeat/{beat}/README.md index 8a1393aea14..b4051bf2ac8 100644 --- a/generate/metricbeat/metricset/{{cookiecutter.beat}}/README.md +++ b/generate/metricbeat/{beat}/README.md @@ -1,6 +1,6 @@ -# {{cookiecutter.project_name}} +# {project_name} -{{cookiecutter.project_name}} is a beat based on metricbeat which was generated with metricbeat/metricset generator. +{project_name} is a beat based on metricbeat which was generated with metricbeat/metricset generator. ## Getting started @@ -16,7 +16,7 @@ It will ask you for the module and metricset name. Insert the name accordingly. To compile your beat run `make`. Then you can run the following command to see the first output: ``` -{{cookiecutter.beat}} -e -d "*" +{beat} -e -d "*" ``` In case further modules are metricsets should be added, run: @@ -68,7 +68,7 @@ git add .gitignore git commit -m "Add git settings" git add . git reset -- .travis.yml -git commit -m "Add {{cookiecutter.beat}}" +git commit -m "Add {beat}" ``` ## Packaging diff --git a/generate/metricbeat/metricset/{{cookiecutter.beat}}/main.go b/generate/metricbeat/{beat}/main.go.tmpl similarity index 54% rename from generate/metricbeat/metricset/{{cookiecutter.beat}}/main.go rename to generate/metricbeat/{beat}/main.go.tmpl index 369a929a686..4f6a4e61ff7 100644 --- a/generate/metricbeat/metricset/{{cookiecutter.beat}}/main.go +++ b/generate/metricbeat/{beat}/main.go.tmpl @@ -7,12 +7,12 @@ import ( "github.com/elastic/beats/metricbeat/beater" // Make sure all your modules and metricsets are linked in this file - _ "{{cookiecutter.beat_path}}/{{cookiecutter.beat}}/include" - // Uncomment the following line to include all official metricbeat module and metricsets - //_ "github.com/elastic/beats/metricbeat/include" + _ "{beat_path}/include" + // Comment out the following line to exclude all official metricbeat modules and metricsets + _ "github.com/elastic/beats/metricbeat/include" ) -var Name = "{{cookiecutter.beat}}" +var Name = "{beat}" func main() { if err := beat.Run(Name, "", beater.New); err != nil { diff --git a/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc b/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc index 36aaad0bae5..ce9327ff7a5 100644 --- a/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc +++ b/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc @@ -12,10 +12,8 @@ must be set up correctly. In addition, the following tools are quired: * https://www.python.org/downloads/[python] * https://virtualenv.pypa.io/en/stable/[virtualenv] -* https://github.com/audreyr/cookiecutter[cookiecutter] -Virtualenv and Cookiecutter are easiest installed with your package manager or https://pip.pypa.io/en/stable/[pip]. For more details on how to -install cookiecutter, see the http://cookiecutter.readthedocs.io/en/latest/installation.html[cookiecutter installation docs]. +Virtualenv is easiest installed with your package manager or https://pip.pypa.io/en/stable/[pip]. [float] === Step 1 - Get the metricbeat source code @@ -49,7 +47,7 @@ Run the command: [source,bash] ---- -cookiecutter ${GOPATH}/src/github.com/elastic/beats/generate/metricbeat/metricset +python ${GOPATH}/src/github.com/elastic/beats/generate/metricbeat.py ---- When prompted, enter the Beat name and path. From f9666bae7c2f67ff921f594e75254ece3620c843 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 24 Jan 2017 10:50:45 +0100 Subject: [PATCH 11/78] Per prospector configurable pipeline (#3433) This adds a new "pipeline" configuration option to the prospector, which can be used to set the Elasticsearch Ingest Node pipeline from the prospector config. While this was already possible by using format strings in the `pipeline` config from the output, this makes the configuration simpler in many cases and the mechanism is needed for the Filebeat modules. Part of #3159. --- CHANGELOG.asciidoc | 1 + filebeat/_meta/beat.full.yml | 4 ++ .../configuration/filebeat-options.asciidoc | 8 +++ filebeat/filebeat.full.yml | 4 ++ filebeat/harvester/config.go | 1 + filebeat/harvester/log.go | 1 + filebeat/input/event.go | 12 ++++ filebeat/publisher/async.go | 6 +- filebeat/publisher/publisher.go | 9 ++- filebeat/publisher/sync.go | 4 +- filebeat/tests/system/config/filebeat.yml.j2 | 12 +++- filebeat/tests/system/test_modules.py | 62 +++++++++++++++++++ libbeat/publisher/client.go | 2 +- 13 files changed, 117 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 84abfc0d25d..edad6ec64b4 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -93,6 +93,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Filebeat* - Add enabled config option to prospectors. {pull}3157[3157] - Add target option for decoded_json_field. {pull}3169[3169] +- Add the `pipeline` config option at the prospector level, for configuring the Ingest Node pipeline ID. {pull}3433[3433] *Winlogbeat* diff --git a/filebeat/_meta/beat.full.yml b/filebeat/_meta/beat.full.yml index 7de2e652220..fc9c0f19741 100644 --- a/filebeat/_meta/beat.full.yml +++ b/filebeat/_meta/beat.full.yml @@ -143,6 +143,10 @@ filebeat.prospectors: # this can mean that the first entries of a new file are skipped. #tail_files: false + # The Ingest Node pipeline ID associated with this prospector. If this is set, it + # overwrites the pipeline option from the Elasticsearch output. + #pipeline: + # Experimental: If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the # original for harvesting but will report the symlink name as source. #symlinks: false diff --git a/filebeat/docs/reference/configuration/filebeat-options.asciidoc b/filebeat/docs/reference/configuration/filebeat-options.asciidoc index 91bebc207d9..4edcd59468a 100644 --- a/filebeat/docs/reference/configuration/filebeat-options.asciidoc +++ b/filebeat/docs/reference/configuration/filebeat-options.asciidoc @@ -405,6 +405,14 @@ This option applies to files that Filebeat has not already processed. If you ran NOTE: You can use this setting to avoid indexing old log lines when you run Filebeat on a set of log files for the first time. After the first run, we recommend disabling this option, or you risk losing lines during file rotation. +===== pipeline + +The Ingest Node pipeline ID to set for the events generated by this prospector. + +NOTE: The pipeline ID can also be configured in the Elasticsearch output, but this + option usually results in simpler configuration files. If the pipeline is configured both + in the prospector and in the output, the option from the prospector is the one used. + ===== symlinks experimental[] diff --git a/filebeat/filebeat.full.yml b/filebeat/filebeat.full.yml index 4fec95ecadf..bd2e0ce5e57 100644 --- a/filebeat/filebeat.full.yml +++ b/filebeat/filebeat.full.yml @@ -143,6 +143,10 @@ filebeat.prospectors: # this can mean that the first entries of a new file are skipped. #tail_files: false + # The Ingest Node pipeline ID associated with this prospector. If this is set, it + # overwrites the pipeline option from the Elasticsearch output. + #pipeline: + # Experimental: If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the # original for harvesting but will report the symlink name as source. #symlinks: false diff --git a/filebeat/harvester/config.go b/filebeat/harvester/config.go index e5fcded5292..d1baaf9a638 100644 --- a/filebeat/harvester/config.go +++ b/filebeat/harvester/config.go @@ -52,6 +52,7 @@ type harvesterConfig struct { MaxBytes int `config:"max_bytes" validate:"min=0,nonzero"` Multiline *reader.MultilineConfig `config:"multiline"` JSON *reader.JSONConfig `config:"json"` + Pipeline string `config:"pipeline"` } func (config *harvesterConfig) Validate() error { diff --git a/filebeat/harvester/log.go b/filebeat/harvester/log.go index afbd0a6bd89..a47bf67809c 100644 --- a/filebeat/harvester/log.go +++ b/filebeat/harvester/log.go @@ -137,6 +137,7 @@ func (h *Harvester) Harvest(r reader.Reader) { event.InputType = h.config.InputType event.DocumentType = h.config.DocumentType event.JSONConfig = h.config.JSON + event.Pipeline = h.config.Pipeline } // Always send event to update state, also if lines was skipped diff --git a/filebeat/input/event.go b/filebeat/input/event.go index 415de417842..3d9a8852600 100644 --- a/filebeat/input/event.go +++ b/filebeat/input/event.go @@ -20,6 +20,7 @@ type Event struct { JSONConfig *reader.JSONConfig State file.State Data common.MapStr // Use in readers to add data to the event + Pipeline string } func NewEvent(state file.State) *Event { @@ -58,6 +59,17 @@ func (e *Event) ToMapStr() common.MapStr { return event } +// Metadata creates a common.MapStr containing the metadata to +// be associated with the event. +func (e *Event) Metadata() common.MapStr { + if e.Pipeline != "" { + return common.MapStr{ + "pipeline": e.Pipeline, + } + } + return nil +} + // HasData returns true if the event itself contains data // Events without data are only state updates func (e *Event) HasData() bool { diff --git a/filebeat/publisher/async.go b/filebeat/publisher/async.go index 93ed5e5ceb1..38ef9af77ba 100644 --- a/filebeat/publisher/async.go +++ b/filebeat/publisher/async.go @@ -84,10 +84,12 @@ func (p *asyncLogPublisher) Start() { flag: 0, events: events, } + dataEvents, meta := getDataEvents(events) p.client.PublishEvents( - getDataEvents(events), + dataEvents, publisher.Signal(batch), - publisher.Guaranteed) + publisher.Guaranteed, + publisher.MetadataBatch(meta)) p.active.append(batch) case <-ticker.C: diff --git a/filebeat/publisher/publisher.go b/filebeat/publisher/publisher.go index 07bbf5fefb9..914208b9679 100644 --- a/filebeat/publisher/publisher.go +++ b/filebeat/publisher/publisher.go @@ -45,12 +45,15 @@ var ( ) // getDataEvents returns all events which contain data (not only state updates) -func getDataEvents(events []*input.Event) []common.MapStr { - dataEvents := make([]common.MapStr, 0, len(events)) +// together with their associated metadata +func getDataEvents(events []*input.Event) (dataEvents []common.MapStr, meta []common.MapStr) { + dataEvents = make([]common.MapStr, 0, len(events)) + meta = make([]common.MapStr, 0, len(events)) for _, event := range events { if event.HasData() { dataEvents = append(dataEvents, event.ToMapStr()) + meta = append(meta, event.Metadata()) } } - return dataEvents + return dataEvents, meta } diff --git a/filebeat/publisher/sync.go b/filebeat/publisher/sync.go index 9aba6f925c1..5b4885959dc 100644 --- a/filebeat/publisher/sync.go +++ b/filebeat/publisher/sync.go @@ -58,7 +58,9 @@ func (p *syncLogPublisher) Publish() error { case events = <-p.in: } - ok := p.client.PublishEvents(getDataEvents(events), publisher.Sync, publisher.Guaranteed) + dataEvents, meta := getDataEvents(events) + ok := p.client.PublishEvents(dataEvents, publisher.Sync, publisher.Guaranteed, + publisher.MetadataBatch(meta)) if !ok { // PublishEvents will only returns false, if p.client has been closed. return sigPublisherStop diff --git a/filebeat/tests/system/config/filebeat.yml.j2 b/filebeat/tests/system/config/filebeat.yml.j2 index 5b34221f828..63639b3209d 100644 --- a/filebeat/tests/system/config/filebeat.yml.j2 +++ b/filebeat/tests/system/config/filebeat.yml.j2 @@ -28,6 +28,7 @@ filebeat.prospectors: clean_removed: {{clean_removed}} harvester_limit: {{harvester_limit | default(0) }} symlinks: {{symlinks}} + pipeline: {{pipeline}} {% if fields %} fields: @@ -138,11 +139,18 @@ processors: # Configure what outputs to use when sending the data collected by the beat. # Multiple outputs may be used. -#------------------------------- File output ---------------------------------- -{%- if logstash %} +{%- if elasticsearch %} +#------------------------------- Elasticsearch output ---------------------------- +output.elasticsearch: + hosts: ["{{ elasticsearch.host }}"] + pipeline: {{elasticsearch.pipeline}} + index: {{elasticsearch.index}} +{%- elif logstash %} +#------------------------------- Logstash output --------------------------------- output.logstash: hosts: ["{{ logstash.host }}"] {%- else %} +#------------------------------- File output ---------------------------------- output.file: path: {{ output_file_path|default(beat.working_dir + "/output") }} filename: "{{ output_file_filename|default("filebeat") }}" diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 845b6dff02c..2f439a6418a 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -110,3 +110,65 @@ def run_on_file(self, module, fileset, test_file, cfgfile): if not found: raise Exception("The following expected object was" + " not found: {}".format(obj)) + + @unittest.skipIf(not INTEGRATION_TESTS or + os.getenv("TESTING_ENVIRONMENT") == "2x", + "integration test not available on 2.x") + def test_prospector_pipeline_config(self): + """ + Tests that the pipeline configured in the prospector overwrites + the one from the output. + """ + self.init() + index_name = "filebeat-test-prospector" + try: + self.es.indices.delete(index=index_name) + except: + pass + self.wait_until(lambda: not self.es.indices.exists(index_name)) + + self.render_config_template( + path=os.path.abspath(self.working_dir) + "/log/*", + elasticsearch=dict( + host=self.elasticsearch_url, + pipeline="estest", + index=index_name), + pipeline="test", + ) + + os.mkdir(self.working_dir + "/log/") + testfile = self.working_dir + "/log/test.log" + with open(testfile, 'a') as file: + file.write("Hello World1\n") + + # put pipeline + self.es.transport.perform_request("PUT", "/_ingest/pipeline/test", + body={ + "processors": [{ + "set": { + "field": "x-pipeline", + "value": "test-pipeline", + } + }]}) + + filebeat = self.start_beat() + + # Wait until the event is in ES + self.wait_until(lambda: self.es.indices.exists(index_name)) + + def search_objects(): + try: + self.es.indices.refresh(index=index_name) + res = self.es.search(index=index_name, + body={"query": {"match_all": {}}}) + return [o["_source"] for o in res["hits"]["hits"]] + except: + return [] + + self.wait_until(lambda: len(search_objects()) > 0, max_timeout=20) + filebeat.check_kill_and_wait() + + objects = search_objects() + assert len(objects) == 1 + o = objects[0] + assert o["x-pipeline"] == "test-pipeline" diff --git a/libbeat/publisher/client.go b/libbeat/publisher/client.go index 246151a96a1..2001f74b790 100644 --- a/libbeat/publisher/client.go +++ b/libbeat/publisher/client.go @@ -238,5 +238,5 @@ func MakeContext(opts []ClientOption) ([]common.MapStr, Context) { } } } - return nil, ctx + return meta, ctx } From 58ed51f8d6fe70092b08900f98cb791b9019bcbb Mon Sep 17 00:00:00 2001 From: Thiago Souza Date: Tue, 24 Jan 2017 10:07:23 -0200 Subject: [PATCH 12/78] New PHP-FPM metricbeat module (#3415) --- metricbeat/_meta/beat.full.yml | 8 ++ metricbeat/docs/fields.asciidoc | 85 +++++++++++++++++++ metricbeat/docs/modules/php_fpm.asciidoc | 61 +++++++++++++ metricbeat/docs/modules/php_fpm/pool.asciidoc | 19 +++++ metricbeat/docs/modules_list.asciidoc | 2 + metricbeat/include/list.go | 2 + metricbeat/metricbeat.full.yml | 8 ++ metricbeat/metricbeat.template-es2x.json | 36 ++++++++ metricbeat/metricbeat.template.json | 35 ++++++++ metricbeat/module/php_fpm/_meta/Dockerfile | 4 + metricbeat/module/php_fpm/_meta/config.yml | 6 ++ metricbeat/module/php_fpm/_meta/docs.asciidoc | 28 ++++++ metricbeat/module/php_fpm/_meta/fields.yml | 14 +++ metricbeat/module/php_fpm/_meta/php-fpm.conf | 16 ++++ metricbeat/module/php_fpm/doc.go | 4 + metricbeat/module/php_fpm/php_fpm.go | 59 +++++++++++++ metricbeat/module/php_fpm/php_fpm_test.go | 30 +++++++ .../module/php_fpm/pool/_meta/data.json | 25 ++++++ .../module/php_fpm/pool/_meta/docs.asciidoc | 3 + .../module/php_fpm/pool/_meta/fields.yml | 53 ++++++++++++ metricbeat/module/php_fpm/pool/data.go | 18 ++++ metricbeat/module/php_fpm/pool/pool.go | 74 ++++++++++++++++ .../php_fpm/pool/pool_integration_test.go | 25 ++++++ 23 files changed, 615 insertions(+) create mode 100644 metricbeat/docs/modules/php_fpm.asciidoc create mode 100644 metricbeat/docs/modules/php_fpm/pool.asciidoc create mode 100644 metricbeat/module/php_fpm/_meta/Dockerfile create mode 100644 metricbeat/module/php_fpm/_meta/config.yml create mode 100644 metricbeat/module/php_fpm/_meta/docs.asciidoc create mode 100644 metricbeat/module/php_fpm/_meta/fields.yml create mode 100644 metricbeat/module/php_fpm/_meta/php-fpm.conf create mode 100644 metricbeat/module/php_fpm/doc.go create mode 100644 metricbeat/module/php_fpm/php_fpm.go create mode 100644 metricbeat/module/php_fpm/php_fpm_test.go create mode 100644 metricbeat/module/php_fpm/pool/_meta/data.json create mode 100644 metricbeat/module/php_fpm/pool/_meta/docs.asciidoc create mode 100644 metricbeat/module/php_fpm/pool/_meta/fields.yml create mode 100644 metricbeat/module/php_fpm/pool/data.go create mode 100644 metricbeat/module/php_fpm/pool/pool.go create mode 100644 metricbeat/module/php_fpm/pool/pool_integration_test.go diff --git a/metricbeat/_meta/beat.full.yml b/metricbeat/_meta/beat.full.yml index f4065b153c0..e93bebd8bdf 100644 --- a/metricbeat/_meta/beat.full.yml +++ b/metricbeat/_meta/beat.full.yml @@ -200,6 +200,14 @@ metricbeat.modules: # Path to server status. Default server-status #server_status_path: "server-status" +#------------------------------- php_fpm Module ------------------------------ +#- module: php_fpm + #metricsets: ["pool"] + #enabled: true + #period: 10s + #status_path: "/status" + #hosts: ["localhost:8080"] + #----------------------------- PostgreSQL Module ----------------------------- #- module: postgresql #metricsets: diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 1afc271c34d..72222092b27 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -23,6 +23,7 @@ grouped in the following categories: * <> * <> * <> +* <> * <> * <> * <> @@ -3802,6 +3803,90 @@ type: long The current number of idle client connections waiting for a request. +[[exported-fields-php_fpm]] +== php_fpm Fields + +PHP-FPM server status metrics collected from PHP-FPM. +experimental[] + + + +[float] +== php_fpm Fields + +`php_fpm` contains the metrics that were obtained from PHP-FPM status page call. + + + +[float] +== pool Fields + +`pool` contains the metrics that were obtained from the PHP-FPM process pool. + + + +[float] +=== php_fpm.pool.pool + +type: keyword + +The name of the pool. + + +[float] +== connections Fields + +Connection state specific statistics. + + + +[float] +=== php_fpm.pool.connections.accepted + +type: long + +The number of incoming requests that the PHP-FPM server has accepted; when a connection is accepted it is removed from the listen queue. + + +[float] +=== php_fpm.pool.connections.queued + +type: long + +The current number of connections that have been initiated, but not yet accepted. If this value is non-zero it typically means that all the available server processes are currently busy, and there are no processes available to serve the next request. Raising `pm.max_children` (provided the server can handle it) should help keep this number low. This property follows from the fact that PHP-FPM listens via a socket (TCP or file based), and thus inherits some of the characteristics of sockets. + + +[float] +== processes Fields + +Process state specific statistics. + + + +[float] +=== php_fpm.pool.processes.idle + +type: long + +The number of servers in the `waiting to process` state (i.e. not currently serving a page). This value should fall between the `pm.min_spare_servers` and `pm.max_spare_servers` values when the process manager is `dynamic`. + + +[float] +=== php_fpm.pool.processes.active + +type: long + +The number of servers current processing a page - the minimum is `1` (so even on a fully idle server, the result will be not read `0`). + + +[float] +=== php_fpm.pool.slow_requests + +type: long + +The number of times a request execution time has exceeded `request_slowlog_timeout`. + + [[exported-fields-postgresql]] == PostgreSQL Fields diff --git a/metricbeat/docs/modules/php_fpm.asciidoc b/metricbeat/docs/modules/php_fpm.asciidoc new file mode 100644 index 00000000000..2440b1935f0 --- /dev/null +++ b/metricbeat/docs/modules/php_fpm.asciidoc @@ -0,0 +1,61 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-module-php_fpm]] +== PHP-FPM Module + +This module periodically fetches metrics from https://php-fpm.org[PHP-FPM] +servers. + +[float] +=== Module-Specific Configuration Notes + +You need to enable the PHP-FPM status page by properly configuring +`pm.status_path`. + +Here is a sample nginx configuration to forward requests to the PHP-FPM status +page (assuming `pm.status_path` is configured with default value `/status`): +```nginx +location ~ /status { + allow 127.0.0.1; + deny all; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; +} +``` + +[float] +=== Compatibility + +The PHP-FPM metricsets were tested with PHP 5.6.29 and are expected to +work with all versions >= 5. + + +[float] +=== Example Configuration + +The php_fpm module supports the standard configuration options that are described +in <>. Here is an example configuration: + +[source,yaml] +---- +metricbeat.modules: +#- module: php_fpm + #metricsets: ["pool"] + #enabled: true + #period: 10s + #status_path: "/status" + #hosts: ["localhost:8080"] +---- + +[float] +=== Metricsets + +The following metricsets are available: + +* <> + +include::php_fpm/pool.asciidoc[] + diff --git a/metricbeat/docs/modules/php_fpm/pool.asciidoc b/metricbeat/docs/modules/php_fpm/pool.asciidoc new file mode 100644 index 00000000000..2c147e2e7e5 --- /dev/null +++ b/metricbeat/docs/modules/php_fpm/pool.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-php_fpm-pool]] +include::../../../module/php_fpm/pool/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/php_fpm/pool/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index 74b85359d7c..6d2b1b45333 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -10,6 +10,7 @@ This file is generated! See scripts/docs_collector.py * <> * <> * <> + * <> * <> * <> * <> @@ -27,6 +28,7 @@ include::modules/kafka.asciidoc[] include::modules/mongodb.asciidoc[] include::modules/mysql.asciidoc[] include::modules/nginx.asciidoc[] +include::modules/php_fpm.asciidoc[] include::modules/postgresql.asciidoc[] include::modules/prometheus.asciidoc[] include::modules/redis.asciidoc[] diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index 3d88f2937d8..aec19892987 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -34,6 +34,8 @@ import ( _ "github.com/elastic/beats/metricbeat/module/mysql/status" _ "github.com/elastic/beats/metricbeat/module/nginx" _ "github.com/elastic/beats/metricbeat/module/nginx/stubstatus" + _ "github.com/elastic/beats/metricbeat/module/php_fpm" + _ "github.com/elastic/beats/metricbeat/module/php_fpm/pool" _ "github.com/elastic/beats/metricbeat/module/postgresql" _ "github.com/elastic/beats/metricbeat/module/postgresql/activity" _ "github.com/elastic/beats/metricbeat/module/postgresql/bgwriter" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 683008110ed..ede25a1c8ed 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -200,6 +200,14 @@ metricbeat.modules: # Path to server status. Default server-status #server_status_path: "server-status" +#------------------------------- php_fpm Module ------------------------------ +#- module: php_fpm + #metricsets: ["pool"] + #enabled: true + #period: 10s + #status_path: "/status" + #hosts: ["localhost:8080"] + #----------------------------- PostgreSQL Module ----------------------------- #- module: postgresql #metricsets: diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index f3d85fbb414..67d90128a55 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -2168,6 +2168,42 @@ } } }, + "php_fpm": { + "properties": { + "pool": { + "properties": { + "connections": { + "properties": { + "accepted": { + "type": "long" + }, + "queued": { + "type": "long" + } + } + }, + "pool": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "processes": { + "properties": { + "active": { + "type": "long" + }, + "idle": { + "type": "long" + } + } + }, + "slow_requests": { + "type": "long" + } + } + } + } + }, "postgresql": { "properties": { "activity": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index d42d1cd726d..5d4b27fb515 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -2147,6 +2147,41 @@ } } }, + "php_fpm": { + "properties": { + "pool": { + "properties": { + "connections": { + "properties": { + "accepted": { + "type": "long" + }, + "queued": { + "type": "long" + } + } + }, + "pool": { + "ignore_above": 1024, + "type": "keyword" + }, + "processes": { + "properties": { + "active": { + "type": "long" + }, + "idle": { + "type": "long" + } + } + }, + "slow_requests": { + "type": "long" + } + } + } + } + }, "postgresql": { "properties": { "activity": { diff --git a/metricbeat/module/php_fpm/_meta/Dockerfile b/metricbeat/module/php_fpm/_meta/Dockerfile new file mode 100644 index 00000000000..7e70cdb2b8a --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/Dockerfile @@ -0,0 +1,4 @@ +FROM richarvey/nginx-php-fpm + +RUN echo "pm.status_path = /status" >> /etc/php7/php-fpm.d/www.conf +ADD ./php-fpm.conf /etc/nginx/sites-enabled diff --git a/metricbeat/module/php_fpm/_meta/config.yml b/metricbeat/module/php_fpm/_meta/config.yml new file mode 100644 index 00000000000..89527a864c4 --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/config.yml @@ -0,0 +1,6 @@ +#- module: php_fpm + #metricsets: ["pool"] + #enabled: true + #period: 10s + #status_path: "/status" + #hosts: ["localhost:8080"] diff --git a/metricbeat/module/php_fpm/_meta/docs.asciidoc b/metricbeat/module/php_fpm/_meta/docs.asciidoc new file mode 100644 index 00000000000..4cd1272731a --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/docs.asciidoc @@ -0,0 +1,28 @@ +== PHP-FPM Module + +This module periodically fetches metrics from https://php-fpm.org[PHP-FPM] +servers. + +[float] +=== Module-Specific Configuration Notes + +You need to enable the PHP-FPM status page by properly configuring +`pm.status_path`. + +Here is a sample nginx configuration to forward requests to the PHP-FPM status +page (assuming `pm.status_path` is configured with default value `/status`): +```nginx +location ~ /status { + allow 127.0.0.1; + deny all; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; +} +``` + +[float] +=== Compatibility + +The PHP-FPM metricsets were tested with PHP 5.6.29 and are expected to +work with all versions >= 5. diff --git a/metricbeat/module/php_fpm/_meta/fields.yml b/metricbeat/module/php_fpm/_meta/fields.yml new file mode 100644 index 00000000000..a8a8ac8ea36 --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/fields.yml @@ -0,0 +1,14 @@ +- key: php_fpm + title: "php_fpm" + description: > + PHP-FPM server status metrics collected from PHP-FPM. + + experimental[] + short_config: false + fields: + - name: php_fpm + type: group + description: > + `php_fpm` contains the metrics that were obtained from PHP-FPM status + page call. + fields: diff --git a/metricbeat/module/php_fpm/_meta/php-fpm.conf b/metricbeat/module/php_fpm/_meta/php-fpm.conf new file mode 100644 index 00000000000..470698d5abd --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/php-fpm.conf @@ -0,0 +1,16 @@ +server { + listen 81; ## listen for ipv4; this line is default and implied + listen [::]:81 default ipv6only=on; ## listen for ipv6 + + # Make site accessible from http://localhost/ + server_name _; + + error_log /dev/stdout info; + access_log /dev/stdout; + + location ~ /status { + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass unix:/var/run/php-fpm.sock; + include fastcgi_params; + } +} diff --git a/metricbeat/module/php_fpm/doc.go b/metricbeat/module/php_fpm/doc.go new file mode 100644 index 00000000000..3fed1f04cf7 --- /dev/null +++ b/metricbeat/module/php_fpm/doc.go @@ -0,0 +1,4 @@ +/* +Package php_fpm is a Metricbeat module that contains MetricSets. +*/ +package php_fpm diff --git a/metricbeat/module/php_fpm/php_fpm.go b/metricbeat/module/php_fpm/php_fpm.go new file mode 100644 index 00000000000..e20d352e3db --- /dev/null +++ b/metricbeat/module/php_fpm/php_fpm.go @@ -0,0 +1,59 @@ +package php_fpm + +import ( + "fmt" + "io" + "net/http" + + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/status" +) + +// HostParser is used for parsing the configured php-fpm hosts. +var HostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + QueryParams: "json", + PathConfigKey: "status_path", +}.Build() + +// StatsClient provides access to php-fpm stats api +type StatsClient struct { + address string + user string + password string + http *http.Client +} + +// NewStatsClient creates a new StatsClient +func NewStatsClient(m mb.BaseMetricSet) *StatsClient { + return &StatsClient{ + address: m.HostData().SanitizedURI, + user: m.HostData().User, + password: m.HostData().Password, + http: &http.Client{Timeout: m.Module().Config().Timeout}, + } +} + +// Fetch php-fpm stats +func (c *StatsClient) Fetch() (io.ReadCloser, error) { + req, err := http.NewRequest("GET", c.address, nil) + if c.user != "" || c.password != "" { + req.SetBasicAuth(c.user, c.password) + } + resp, err := c.http.Do(req) + if err != nil { + return nil, fmt.Errorf("error making http request: %v", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + } + + return resp.Body, nil +} diff --git a/metricbeat/module/php_fpm/php_fpm_test.go b/metricbeat/module/php_fpm/php_fpm_test.go new file mode 100644 index 00000000000..e5e6fe1342d --- /dev/null +++ b/metricbeat/module/php_fpm/php_fpm_test.go @@ -0,0 +1,30 @@ +package php_fpm + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestHostParser(t *testing.T) { + tests := []struct { + host, expected string + }{ + {"localhost", "http://localhost/status?json="}, + {"localhost:123", "http://localhost:123/status?json="}, + {"http://localhost:123", "http://localhost:123/status?json="}, + } + + m := mbtest.NewTestModule(t, map[string]interface{}{}) + + for _, test := range tests { + hi, err := HostParser(m, test.host) + if err != nil { + t.Error("failed on", test.host, err) + continue + } + assert.Equal(t, test.expected, hi.URI) + } +} diff --git a/metricbeat/module/php_fpm/pool/_meta/data.json b/metricbeat/module/php_fpm/pool/_meta/data.json new file mode 100644 index 00000000000..06cb2852633 --- /dev/null +++ b/metricbeat/module/php_fpm/pool/_meta/data.json @@ -0,0 +1,25 @@ +{ + "@timestamp": "2017-01-18T23:57:23.960Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "metricset": { + "host": "localhost:8081", + "module": "php_fpm", + "name": "pool", + "rtt": 1237 + }, + "php_fpm": { + "pool": { + "connections.accepted": 803, + "connections.queued": 0, + "hostname": "localhost:8081", + "pool": "www", + "processes.active": 1, + "processes.idle": 2, + "requests.slow": 0 + } + }, + "type": "metricsets" +} diff --git a/metricbeat/module/php_fpm/pool/_meta/docs.asciidoc b/metricbeat/module/php_fpm/pool/_meta/docs.asciidoc new file mode 100644 index 00000000000..0e158655e08 --- /dev/null +++ b/metricbeat/module/php_fpm/pool/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== php_fpm pool MetricSet + +This is the pool metricset of the module php_fpm. diff --git a/metricbeat/module/php_fpm/pool/_meta/fields.yml b/metricbeat/module/php_fpm/pool/_meta/fields.yml new file mode 100644 index 00000000000..ba22858f91e --- /dev/null +++ b/metricbeat/module/php_fpm/pool/_meta/fields.yml @@ -0,0 +1,53 @@ +- name: pool + type: group + description: > + `pool` contains the metrics that were obtained from the PHP-FPM process + pool. + fields: + - name: pool + type: keyword + description: > + The name of the pool. + - name: connections + type: group + description: > + Connection state specific statistics. + fields: + - name: accepted + type: long + description: > + The number of incoming requests that the PHP-FPM server has accepted; + when a connection is accepted it is removed from the listen queue. + - name: queued + type: long + description: > + The current number of connections that have been initiated, but not + yet accepted. If this value is non-zero it typically means that all + the available server processes are currently busy, and there are no + processes available to serve the next request. Raising + `pm.max_children` (provided the server can handle it) should help + keep this number low. This property follows from the fact that + PHP-FPM listens via a socket (TCP or file based), and thus inherits + some of the characteristics of sockets. + - name: processes + type: group + description: > + Process state specific statistics. + fields: + - name: idle + type: long + description: > + The number of servers in the `waiting to process` state (i.e. not + currently serving a page). This value should fall between the + `pm.min_spare_servers` and `pm.max_spare_servers` values when the + process manager is `dynamic`. + - name: active + type: long + description: > + The number of servers current processing a page - the minimum is `1` + (so even on a fully idle server, the result will be not read `0`). + - name: slow_requests + type: long + description: > + The number of times a request execution time has exceeded + `request_slowlog_timeout`. diff --git a/metricbeat/module/php_fpm/pool/data.go b/metricbeat/module/php_fpm/pool/data.go new file mode 100644 index 00000000000..48a797f36cf --- /dev/null +++ b/metricbeat/module/php_fpm/pool/data.go @@ -0,0 +1,18 @@ +package pool + +type poolStats struct { + Pool string `json:"pool"` + ProcessManager string `json:"process manager"` + StartTime int `json:"start time"` + StartSince int `json:"start since"` + AcceptedConn int `json:"accepted conn"` + ListenQueue int `json:"listen queue"` + MaxListenQueue int `json:"max listen queue"` + ListenQueueLen int `json:"listen queue len"` + IdleProcesses int `json:"idle processes"` + ActiveProcesses int `json:"active processes"` + TotalProcesses int `json:"total processes"` + MaxActiveProcesses int `json:"max active processes"` + MaxChildrenReached int `json:"max children reached"` + SlowRequests int `json:"slow requests"` +} diff --git a/metricbeat/module/php_fpm/pool/pool.go b/metricbeat/module/php_fpm/pool/pool.go new file mode 100644 index 00000000000..982fe1e3899 --- /dev/null +++ b/metricbeat/module/php_fpm/pool/pool.go @@ -0,0 +1,74 @@ +package pool + +import ( + "encoding/json" + "fmt" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + + "github.com/elastic/beats/metricbeat/module/php_fpm" +) + +// init registers the MetricSet with the central registry. +// The New method will be called after the setup of the module and before starting to fetch data +func init() { + if err := mb.Registry.AddMetricSet("php_fpm", "pool", New, php_fpm.HostParser); err != nil { + panic(err) + } +} + +// MetricSet type defines all fields of the MetricSet +// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with +// additional entries. These variables can be used to persist data or configuration between +// multiple fetch calls. +type MetricSet struct { + mb.BaseMetricSet + client *php_fpm.StatsClient // StatsClient that is reused across requests. +} + +// New create a new instance of the MetricSet +// Part of new is also setting up the configuration by processing additional +// configuration entries if needed. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The php-fpm pool metricset is experimental") + return &MetricSet{ + BaseMetricSet: base, + client: php_fpm.NewStatsClient(base), + }, nil +} + +// Fetch methods implements the data gathering and data conversion to the right format +// It returns the event which is then forward to the output. In case of an error, a +// descriptive error must be returned. +func (m *MetricSet) Fetch() (common.MapStr, error) { + body, err := m.client.Fetch() + + if err != nil { + return nil, err + } + + defer body.Close() + + stats := &poolStats{} + err = json.NewDecoder(body).Decode(stats) + if err != nil { + return nil, fmt.Errorf("error parsing json: %v", err) + } + + return common.MapStr{ + "hostname": m.Host(), + + "pool": stats.Pool, + "connections": common.MapStr{ + "queue": stats.ListenQueue, + "accepted": stats.AcceptedConn, + }, + "processes": common.MapStr{ + "idle": stats.IdleProcesses, + "active": stats.ActiveProcesses, + }, + "slow_requests": stats.SlowRequests, + }, nil +} diff --git a/metricbeat/module/php_fpm/pool/pool_integration_test.go b/metricbeat/module/php_fpm/pool/pool_integration_test.go new file mode 100644 index 00000000000..b5026cfa7d2 --- /dev/null +++ b/metricbeat/module/php_fpm/pool/pool_integration_test.go @@ -0,0 +1,25 @@ +// +build integration + +package pool + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventFetcher(t, getConfig()) + err := mbtest.WriteEvent(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "php_fpm", + "metricsets": []string{"pool"}, + "hosts": []string{"127.0.0.1:81"}, + } +} From 868b6227a4bd543ac77caa1850e36b29347affac Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 24 Jan 2017 14:29:01 +0100 Subject: [PATCH 13/78] Move tcp protocol generator to packetbeat (#3447) The tcp procol generator is packetbeat specific. Similar to module and metricset generator it belongs inside the beat. * The generator was migrated from cookiecutter to a python script to not have additional dependency. * A makefile target was added to simplify the generation In the future collect should fetch all protocols and add them automatically to the import to have it the same as for metricbeat. In addition it should be possible based on the global generator to create a packetbeat "shell" to put in own protocols. --- .../packetbeat/tcp-protocol/cookiecutter.json | 6 -- packetbeat/Makefile | 4 + packetbeat/scripts/create_tcp_protocol.py | 87 +++++++++++++++++++ .../scripts}/tcp-protocol/README.md | 8 +- .../tcp-protocol/{protocol}/config.go.tmpl | 8 +- .../tcp-protocol/{protocol}/parser.go.tmpl | 2 +- .../tcp-protocol/{protocol}/pub.go.tmpl | 4 +- .../tcp-protocol/{protocol}/trans.go.tmpl | 2 +- .../{protocol}/{protocol}.go.tmpl | 64 +++++++------- 9 files changed, 135 insertions(+), 50 deletions(-) delete mode 100644 generate/packetbeat/tcp-protocol/cookiecutter.json create mode 100644 packetbeat/scripts/create_tcp_protocol.py rename {generate/packetbeat => packetbeat/scripts}/tcp-protocol/README.md (92%) rename generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/config.go => packetbeat/scripts/tcp-protocol/{protocol}/config.go.tmpl (59%) rename generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/parser.go => packetbeat/scripts/tcp-protocol/{protocol}/parser.go.tmpl (98%) rename generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/pub.go => packetbeat/scripts/tcp-protocol/{protocol}/pub.go.tmpl (94%) rename generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/trans.go => packetbeat/scripts/tcp-protocol/{protocol}/trans.go.tmpl (99%) rename generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/{{cookiecutter.module}}.go => packetbeat/scripts/tcp-protocol/{protocol}/{protocol}.go.tmpl (56%) diff --git a/generate/packetbeat/tcp-protocol/cookiecutter.json b/generate/packetbeat/tcp-protocol/cookiecutter.json deleted file mode 100644 index 5aabe752549..00000000000 --- a/generate/packetbeat/tcp-protocol/cookiecutter.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "protocol": "", - "module": "{{ cookiecutter.protocol|lower }}", - "plugin_type": "{{ cookiecutter.protocol|lower }}Plugin", - "plugin_var": "{{ cookiecutter.plugin_type[0]|lower }}p" -} diff --git a/packetbeat/Makefile b/packetbeat/Makefile index e22678c080a..e35aa112e9d 100644 --- a/packetbeat/Makefile +++ b/packetbeat/Makefile @@ -38,3 +38,7 @@ fields: .PHONY: benchmark benchmark: go test -short -bench=. ./... -cpu=2 + +.PHONY: create-tcp-protocol +create-tcp-protocol: + python scripts/create_tcp_protocol.py diff --git a/packetbeat/scripts/create_tcp_protocol.py b/packetbeat/scripts/create_tcp_protocol.py new file mode 100644 index 00000000000..d119e988009 --- /dev/null +++ b/packetbeat/scripts/create_tcp_protocol.py @@ -0,0 +1,87 @@ +import os +import argparse + +# Creates a tcp protocol + +protocol = "" +plugin_type = "" +plugin_var = "" + +def generate_protocol(): + read_input() + process_file() + +def read_input(): + """Requests input form the command line for empty variables if needed. + """ + global protocol, plugin_type, plugin_var + + if protocol == "": + protocol = raw_input("Protocol Name [exampletcp]: ") or "exampletcp" + + protocol = protocol.lower() + + plugin_type = protocol + "Plugin" + plugin_var = protocol[0] + "p" + + +def process_file(): + + # Load path information + generator_path = os.path.dirname(os.path.realpath(__file__)) + go_path = os.environ['GOPATH'] + + for root, dirs, files in os.walk(generator_path + '/tcp-protocol/{protocol}'): + + for file in files: + + full_path = root + "/" + file + + ## load file + content = "" + with open(full_path) as f: + content = f.read() + + # process content + content = replace_variables(content) + + # Write new path + new_path = replace_variables(full_path).replace(".go.tmpl", ".go") + + # remove generator info from path + file_path = new_path.replace(generator_path + "/tcp-protocol/", "") + + # New file path to write file content to + write_file = "protos/" + file_path + + # Create parent directory if it does not exist yet + dir = os.path.dirname(write_file) + if not os.path.exists(dir): + os.makedirs(dir) + + # Write file to new location + with open(write_file, 'w') as f: + f.write(content) + +def replace_variables(content): + """Replace all template variables with the actual values + """ + return content.replace("{protocol}", protocol) \ + .replace("{plugin_var}", plugin_var) \ + .replace("{plugin_type}", plugin_type) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="Creates a beat") + parser.add_argument("--protocol", help="Protocol name") + + args = parser.parse_args() + + + if args.protocol is not None: + protocol = args.protocol + + generate_protocol() + + diff --git a/generate/packetbeat/tcp-protocol/README.md b/packetbeat/scripts/tcp-protocol/README.md similarity index 92% rename from generate/packetbeat/tcp-protocol/README.md rename to packetbeat/scripts/tcp-protocol/README.md index a6661405eba..8f931ec053a 100644 --- a/generate/packetbeat/tcp-protocol/README.md +++ b/packetbeat/scripts/tcp-protocol/README.md @@ -7,12 +7,12 @@ want to create the protocol analyzer (stand-alone, within packetbeat based project or packetbeat itself): ``` -cookiecutter ${GOPATH}/src/github.com/elastic/beats/generate/packetbeat/tcp-protocol +python ${GOPATH}/src/github.com/elastic/beats/packetbeat/scripts/create_tcp_protocol.py ``` Note: If you have multiple go paths use `${GOPATH%%:*}`instead of `${GOPATH}`. -This requires [python](https://www.python.org/downloads/) and [cookiecutter](https://github.com/audreyr/cookiecutter) to be installed. More details on how to install cookiecutter can be found [here](http://cookiecutter.readthedocs.io/en/latest/installation.html). +This requires [python](https://www.python.org/downloads/) to be installed. ## Tutorial (TODO): @@ -103,7 +103,7 @@ Create analyzer skeleton from code generator template. ``` $ cd ${GOPATH}/src/github.com/elastic/beats/packetbeat/protos - $ cookiecutter ${GOPATH}/src/github.com/elastic/beats/generate/packetbeat/tcp-protocol + $ python ${GOPATH}/src/github.com/elastic/beats/packetbeat/script/create_tcp_protocol.py ``` Load plugin into packetbeat by adding `_ "github.com/elastic/beats/packetbeat/protos/echo"` to packetbeat import list in `$GOPATH/src/github.com/elastic/beats/packetbeat/main.go` @@ -152,7 +152,7 @@ Create protocol analyzer module (use name ‘echo’ for new protocol): ``` $ mkdir proto $ cd proto -$ cookiecutter ${GOPATH}/src/github.com/elastic/beats/generate/packetbeat/tcp-protocol +$ python ${GOPATH}/src/github.com/elastic/beats/packetbeat/script/create_tcp_protocol.py ``` ### 3 Implement application layer analyzer diff --git a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/config.go b/packetbeat/scripts/tcp-protocol/{protocol}/config.go.tmpl similarity index 59% rename from generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/config.go rename to packetbeat/scripts/tcp-protocol/{protocol}/config.go.tmpl index 50bd65ad5cc..4e8ff970284 100644 --- a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/config.go +++ b/packetbeat/scripts/tcp-protocol/{protocol}/config.go.tmpl @@ -1,22 +1,22 @@ -package {{ cookiecutter.module }} +package {protocol} import ( "github.com/elastic/beats/packetbeat/config" "github.com/elastic/beats/packetbeat/protos" ) -type {{ cookiecutter.module }}Config struct { +type {protocol}Config struct { config.ProtocolCommon `config:",inline"` } var ( - defaultConfig = {{ cookiecutter.module }}Config{ + defaultConfig = {protocol}Config{ ProtocolCommon: config.ProtocolCommon{ TransactionTimeout: protos.DefaultTransactionExpiration, }, } ) -func (c *{{ cookiecutter.module }}Config) Validate() error { +func (c *{protocol}Config) Validate() error { return nil } diff --git a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/parser.go b/packetbeat/scripts/tcp-protocol/{protocol}/parser.go.tmpl similarity index 98% rename from generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/parser.go rename to packetbeat/scripts/tcp-protocol/{protocol}/parser.go.tmpl index e45565ca02f..dc114289482 100644 --- a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/parser.go +++ b/packetbeat/scripts/tcp-protocol/{protocol}/parser.go.tmpl @@ -1,4 +1,4 @@ -package {{ cookiecutter.module }} +package {protocol} import ( "errors" diff --git a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/pub.go b/packetbeat/scripts/tcp-protocol/{protocol}/pub.go.tmpl similarity index 94% rename from generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/pub.go rename to packetbeat/scripts/tcp-protocol/{protocol}/pub.go.tmpl index 46de6670dc0..0eec39662eb 100644 --- a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/pub.go +++ b/packetbeat/scripts/tcp-protocol/{protocol}/pub.go.tmpl @@ -1,4 +1,4 @@ -package {{ cookiecutter.module }} +package {protocol} import ( "github.com/elastic/beats/libbeat/common" @@ -42,7 +42,7 @@ func (pub *transPub) createEvent(requ, resp *message) common.MapStr { event := common.MapStr{ "@timestamp": common.Time(requ.Ts), - "type": "{{ cookiecutter.module }}", + "type": "{protocol}", "status": status, "responsetime": responseTime, "bytes_in": requ.Size, diff --git a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/trans.go b/packetbeat/scripts/tcp-protocol/{protocol}/trans.go.tmpl similarity index 99% rename from generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/trans.go rename to packetbeat/scripts/tcp-protocol/{protocol}/trans.go.tmpl index 052a361e4a6..e234d01ceb6 100644 --- a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/trans.go +++ b/packetbeat/scripts/tcp-protocol/{protocol}/trans.go.tmpl @@ -1,4 +1,4 @@ -package {{ cookiecutter.module }} +package {protocol} import ( "time" diff --git a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/{{cookiecutter.module}}.go b/packetbeat/scripts/tcp-protocol/{protocol}/{protocol}.go.tmpl similarity index 56% rename from generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/{{cookiecutter.module}}.go rename to packetbeat/scripts/tcp-protocol/{protocol}/{protocol}.go.tmpl index 31912e0953d..5bd703f54bb 100644 --- a/generate/packetbeat/tcp-protocol/{{cookiecutter.module}}/{{cookiecutter.module}}.go +++ b/packetbeat/scripts/tcp-protocol/{protocol}/{protocol}.go.tmpl @@ -1,4 +1,4 @@ -package {{ cookiecutter.module }} +package {protocol} import ( "time" @@ -11,8 +11,8 @@ import ( "github.com/elastic/beats/packetbeat/publish" ) -// {{ cookiecutter.plugin_type }} application level protocol analyzer plugin -type {{ cookiecutter.plugin_type }} struct { +// {plugin_type} application level protocol analyzer plugin +type {plugin_type} struct { ports protos.PortsConfig parserConfig parserConfig transConfig transactionConfig @@ -31,7 +31,7 @@ type stream struct { } var ( - debugf = logp.MakeDebug("{{ cookiecutter.module }}") + debugf = logp.MakeDebug("{protocol}") // use isDebug/isDetailed to guard debugf/detailedf to minimize allocations // (garbage collection) when debug log is disabled. @@ -39,16 +39,16 @@ var ( ) func init() { - protos.Register("{{ cookiecutter.module }}", New) + protos.Register("{protocol}", New) } -// New create and initializes a new {{ cookiecutter.protocol }} protocol analyzer instance. +// New create and initializes a new {protocol} protocol analyzer instance. func New( testMode bool, results publish.Transactions, cfg *common.Config, ) (protos.Plugin, error) { - p := &{{ cookiecutter.plugin_type }}{} + p := &{plugin_type}{} config := defaultConfig if !testMode { if err := cfg.Unpack(&config); err != nil { @@ -62,33 +62,33 @@ func New( return p, nil } -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) init(results publish.Transactions, config *{{ cookiecutter.module }}Config) error { - if err := {{ cookiecutter.plugin_var }}.setFromConfig(config); err != nil { +func ({plugin_var} *{plugin_type}) init(results publish.Transactions, config *{protocol}Config) error { + if err := {plugin_var}.setFromConfig(config); err != nil { return err } - {{ cookiecutter.plugin_var }}.pub.results = results + {plugin_var}.pub.results = results isDebug = logp.IsDebug("http") return nil } -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) setFromConfig(config *{{ cookiecutter.module }}Config) error { +func ({plugin_var} *{plugin_type}) setFromConfig(config *{protocol}Config) error { // set module configuration - if err := {{ cookiecutter.plugin_var }}.ports.Set(config.Ports); err != nil { + if err := {plugin_var}.ports.Set(config.Ports); err != nil { return err } // set parser configuration - parser := &{{ cookiecutter.plugin_var }}.parserConfig + parser := &{plugin_var}.parserConfig parser.maxBytes = tcp.TCPMaxDataInStream // set transaction correlator configuration - trans := &{{ cookiecutter.plugin_var }}.transConfig + trans := &{plugin_var}.transConfig trans.transactionTimeout = config.TransactionTimeout // set transaction publisher configuration - pub := &{{ cookiecutter.plugin_var }}.pub + pub := &{plugin_var}.pub pub.sendRequest = config.SendRequest pub.sendResponse = config.SendResponse @@ -97,29 +97,29 @@ func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) setFromConf // ConnectionTimeout returns the per stream connection timeout. // Return <=0 to set default tcp module transaction timeout. -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) ConnectionTimeout() time.Duration { - return {{ cookiecutter.plugin_var }}.transConfig.transactionTimeout +func ({plugin_var} *{plugin_type}) ConnectionTimeout() time.Duration { + return {plugin_var}.transConfig.transactionTimeout } // GetPorts returns the ports numbers packets shall be processed for. -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) GetPorts() []int { - return {{ cookiecutter.plugin_var }}.ports.Ports +func ({plugin_var} *{plugin_type}) GetPorts() []int { + return {plugin_var}.ports.Ports } // Parse processes a TCP packet. Return nil if connection // state shall be dropped (e.g. parser not in sync with tcp stream) -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) Parse( +func ({plugin_var} *{plugin_type}) Parse( pkt *protos.Packet, tcptuple *common.TCPTuple, dir uint8, private protos.ProtocolData, ) protos.ProtocolData { - defer logp.Recover("Parse {{ cookiecutter.plugin_type }} exception") + defer logp.Recover("Parse {plugin_type} exception") - conn := {{ cookiecutter.plugin_var }}.ensureConnection(private) + conn := {plugin_var}.ensureConnection(private) st := conn.streams[dir] if st == nil { st = &stream{} - st.parser.init(&{{ cookiecutter.plugin_var }}.parserConfig, func(msg *message) error { + st.parser.init(&{plugin_var}.parserConfig, func(msg *message) error { return conn.trans.onMessage(tcptuple.IPPort(), dir, msg) }) conn.streams[dir] = st @@ -127,14 +127,14 @@ func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) Parse( if err := st.parser.feed(pkt.Ts, pkt.Payload); err != nil { debugf("%v, dropping TCP stream for error in direction %v.", err, dir) - {{ cookiecutter.plugin_var }}.onDropConnection(conn) + {plugin_var}.onDropConnection(conn) return nil } return conn } // ReceivedFin handles TCP-FIN packet. -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) ReceivedFin( +func ({plugin_var} *{plugin_type}) ReceivedFin( tcptuple *common.TCPTuple, dir uint8, private protos.ProtocolData, ) protos.ProtocolData { @@ -142,13 +142,13 @@ func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) ReceivedFin } // GapInStream handles lost packets in tcp-stream. -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) GapInStream(tcptuple *common.TCPTuple, dir uint8, +func ({plugin_var} *{plugin_type}) GapInStream(tcptuple *common.TCPTuple, dir uint8, nbytes int, private protos.ProtocolData, ) (protos.ProtocolData, bool) { conn := getConnection(private) if conn != nil { - {{ cookiecutter.plugin_var }}.onDropConnection(conn) + {plugin_var}.onDropConnection(conn) } return nil, true @@ -156,14 +156,14 @@ func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) GapInStream // onDropConnection processes and optionally sends incomplete // transaction in case of connection being dropped due to error -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) onDropConnection(conn *connection) { +func ({plugin_var} *{plugin_type}) onDropConnection(conn *connection) { } -func ({{ cookiecutter.plugin_var }} *{{ cookiecutter.plugin_type }}) ensureConnection(private protos.ProtocolData) *connection { +func ({plugin_var} *{plugin_type}) ensureConnection(private protos.ProtocolData) *connection { conn := getConnection(private) if conn == nil { conn = &connection{} - conn.trans.init(&{{ cookiecutter.plugin_var }}.transConfig, {{ cookiecutter.plugin_var }}.pub.onTransaction) + conn.trans.init(&{plugin_var}.transConfig, {plugin_var}.pub.onTransaction) } return conn } @@ -180,11 +180,11 @@ func getConnection(private protos.ProtocolData) *connection { priv, ok := private.(*connection) if !ok { - logp.Warn("{{ cookiecutter.module }} connection type error") + logp.Warn("{protocol} connection type error") return nil } if priv == nil { - logp.Warn("Unexpected: {{ cookiecutter.module }} connection data not set") + logp.Warn("Unexpected: {protocol} connection data not set") return nil } return priv From f9031b936209159bf05ef6de18cd013658c94d0e Mon Sep 17 00:00:00 2001 From: "Amanda H. L. de Andrade" Date: Tue, 24 Jan 2017 14:05:38 -0200 Subject: [PATCH 14/78] Add CEPH Module with health metrics (#3311) --- metricbeat/_meta/beat.full.yml | 7 + metricbeat/docker-compose.yml | 12 ++ metricbeat/docker-entrypoint.sh | 1 + metricbeat/docs/fields.asciidoc | 158 ++++++++++++++++++ metricbeat/docs/modules/ceph.asciidoc | 36 ++++ metricbeat/docs/modules/ceph/health.asciidoc | 19 +++ metricbeat/docs/modules_list.asciidoc | 2 + metricbeat/include/list.go | 2 + metricbeat/metricbeat.full.yml | 7 + metricbeat/metricbeat.template-es2x.json | 112 +++++++++++++ metricbeat/metricbeat.template.json | 108 ++++++++++++ metricbeat/module/ceph/_meta/config.yml | 5 + metricbeat/module/ceph/_meta/docs.asciidoc | 4 + metricbeat/module/ceph/_meta/fields.yml | 12 ++ metricbeat/module/ceph/doc.go | 4 + metricbeat/module/ceph/health/_meta/data.json | 28 ++++ .../module/ceph/health/_meta/docs.asciidoc | 3 + .../module/ceph/health/_meta/fields.yml | 73 ++++++++ metricbeat/module/ceph/health/data.go | 138 +++++++++++++++ metricbeat/module/ceph/health/health.go | 62 +++++++ .../ceph/health/health_integration_test.go | 47 ++++++ metricbeat/module/ceph/health/health_test.go | 89 ++++++++++ .../ceph/health/testdata/sample_response.json | 41 +++++ 23 files changed, 970 insertions(+) create mode 100644 metricbeat/docs/modules/ceph.asciidoc create mode 100644 metricbeat/docs/modules/ceph/health.asciidoc create mode 100644 metricbeat/module/ceph/_meta/config.yml create mode 100644 metricbeat/module/ceph/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/_meta/fields.yml create mode 100644 metricbeat/module/ceph/doc.go create mode 100644 metricbeat/module/ceph/health/_meta/data.json create mode 100644 metricbeat/module/ceph/health/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/health/_meta/fields.yml create mode 100644 metricbeat/module/ceph/health/data.go create mode 100644 metricbeat/module/ceph/health/health.go create mode 100644 metricbeat/module/ceph/health/health_integration_test.go create mode 100644 metricbeat/module/ceph/health/health_test.go create mode 100644 metricbeat/module/ceph/health/testdata/sample_response.json diff --git a/metricbeat/_meta/beat.full.yml b/metricbeat/_meta/beat.full.yml index e93bebd8bdf..957fadb0336 100644 --- a/metricbeat/_meta/beat.full.yml +++ b/metricbeat/_meta/beat.full.yml @@ -94,6 +94,13 @@ metricbeat.modules: # Password of hosts. Empty by default #password: test123 +#-------------------------------- ceph Module -------------------------------- +#- module: ceph +# metricsets: ["health"] +# enabled: true +# period: 10s +# hosts: ["localhost:5000"] + #------------------------------ couchbase Module ----------------------------- #- module: couchbase #metricsets: ["cluster", "node", "bucket"] diff --git a/metricbeat/docker-compose.yml b/metricbeat/docker-compose.yml index 98788275a6c..6fac45b9451 100644 --- a/metricbeat/docker-compose.yml +++ b/metricbeat/docker-compose.yml @@ -4,6 +4,7 @@ services: build: ${PWD}/. depends_on: - apache + - ceph - couchbase - mongodb - haproxy @@ -17,6 +18,8 @@ services: environment: - APACHE_HOST=apache - APACHE_PORT=80 + - CEPH_HOST=ceph + - CEPH_PORT=5000 - COUCHBASE_HOST=couchbase - COUCHBASE_PORT=8091 - COUCHBASE_DSN=http://Administrator:password@couchbase:8091 @@ -53,6 +56,15 @@ services: apache: build: ${PWD}/module/apache/_meta + ceph: + image: ceph/demo + hostname: ceph + environment: + - MON_IP=0.0.0.0 + - CEPH_PUBLIC_NETWORK=0.0.0.0/0 + expose: + - 5000 + couchbase: build: ${PWD}/module/couchbase/_meta diff --git a/metricbeat/docker-entrypoint.sh b/metricbeat/docker-entrypoint.sh index 25087fc7fa3..d5a78b62534 100755 --- a/metricbeat/docker-entrypoint.sh +++ b/metricbeat/docker-entrypoint.sh @@ -23,6 +23,7 @@ waitFor() { # Main waitFor ${APACHE_HOST} ${APACHE_PORT} Apache +waitFor ${CEPH_HOST} ${CEPH_PORT} Ceph waitFor ${COUCHBASE_HOST} ${COUCHBASE_PORT} Couchbase waitFor ${HAPROXY_HOST} ${HAPROXY_PORT} HAProxy waitFor ${KAFKA_HOST} ${KAFKA_PORT} Kafka diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 72222092b27..bdc9f50023c 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -14,6 +14,7 @@ grouped in the following categories: * <> * <> +* <> * <> * <> * <> @@ -412,6 +413,163 @@ type: dict Contains user configurable fields. +[[exported-fields-ceph]] +== ceph Fields + +experimental[] ceph Module + + + +[float] +== ceph Fields + +`ceph` contains the metrics that were scraped from CEPH. + + + +[float] +== health Fields + +health + + + +[float] +=== ceph.health.cluster.overall_status + +type: keyword + +Overall status of the cluster + + +[float] +=== ceph.health.cluster.timechecks.epoch + +type: long + +Map version + + +[float] +=== ceph.health.cluster.timechecks.round.value + +type: long + +timecheck round + + +[float] +=== ceph.health.cluster.timechecks.round.status + +type: keyword + +Status of the round + + +[float] +=== ceph.health.mon.available.pct + +type: long + +Available percent of the MON + + +[float] +=== ceph.health.mon.health + +type: keyword + +Health of the MON + + +[float] +=== ceph.health.mon.available.kb + +type: long + +Available KB of the MON + + +[float] +=== ceph.health.mon.total.kb + +type: long + +Total KB of the MON + + +[float] +=== ceph.health.mon.used.kb + +type: long + +Used KB of the MON + + +[float] +=== ceph.health.mon.last_updated + +type: date + +Time when was updated + + +[float] +=== ceph.health.mon.name + +type: keyword + +Name of the MON + + +[float] +=== ceph.health.mon.store_stats.log.bytes + +type: long + +format: bytes + +Log bytes of MON + + +[float] +=== ceph.health.mon.store_stats.misc.bytes + +type: long + +format: bytes + +Misc bytes of MON + + +[float] +=== ceph.health.mon.store_stats.sst.bytes + +type: long + +format: bytes + +SST bytes of MON + + +[float] +=== ceph.health.mon.store_stats.total.bytes + +type: long + +format: bytes + +Total bytes of MON + + +[float] +=== ceph.health.mon.store_stats.last_updated + +type: long + +Last updated + + [[exported-fields-cloud]] == Cloud Provider Metadata Fields diff --git a/metricbeat/docs/modules/ceph.asciidoc b/metricbeat/docs/modules/ceph.asciidoc new file mode 100644 index 00000000000..97265d9143d --- /dev/null +++ b/metricbeat/docs/modules/ceph.asciidoc @@ -0,0 +1,36 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-module-ceph]] +== ceph Module + +This is the ceph Module. Metrics are collected submitting HTTP GET requests to ceph-rest-api. +Reference: http://docs.ceph.com/docs/master/man/8/ceph-rest-api/ + + +[float] +=== Example Configuration + +The ceph module supports the standard configuration options that are described +in <>. Here is an example configuration: + +[source,yaml] +---- +metricbeat.modules: +#- module: ceph +# metricsets: ["health"] +# enabled: true +# period: 10s +# hosts: ["localhost:5000"] +---- + +[float] +=== Metricsets + +The following metricsets are available: + +* <> + +include::ceph/health.asciidoc[] + diff --git a/metricbeat/docs/modules/ceph/health.asciidoc b/metricbeat/docs/modules/ceph/health.asciidoc new file mode 100644 index 00000000000..0c0765d4e4f --- /dev/null +++ b/metricbeat/docs/modules/ceph/health.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-ceph-health]] +include::../../../module/ceph/health/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/ceph/health/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index 6d2b1b45333..c26d71234ee 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -3,6 +3,7 @@ This file is generated! See scripts/docs_collector.py //// * <> + * <> * <> * <> * <> @@ -21,6 +22,7 @@ This file is generated! See scripts/docs_collector.py -- include::modules/apache.asciidoc[] +include::modules/ceph.asciidoc[] include::modules/couchbase.asciidoc[] include::modules/docker.asciidoc[] include::modules/haproxy.asciidoc[] diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index aec19892987..810ce066367 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -10,6 +10,8 @@ import ( // This list is automatically generated by `make imports` _ "github.com/elastic/beats/metricbeat/module/apache" _ "github.com/elastic/beats/metricbeat/module/apache/status" + _ "github.com/elastic/beats/metricbeat/module/ceph" + _ "github.com/elastic/beats/metricbeat/module/ceph/health" _ "github.com/elastic/beats/metricbeat/module/couchbase" _ "github.com/elastic/beats/metricbeat/module/couchbase/bucket" _ "github.com/elastic/beats/metricbeat/module/couchbase/cluster" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index ede25a1c8ed..11109809a3d 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -94,6 +94,13 @@ metricbeat.modules: # Password of hosts. Empty by default #password: test123 +#-------------------------------- ceph Module -------------------------------- +#- module: ceph +# metricsets: ["health"] +# enabled: true +# period: 10s +# hosts: ["localhost:5000"] + #------------------------------ couchbase Module ----------------------------- #- module: couchbase #metricsets: ["cluster", "node", "bucket"] diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 67d90128a55..8d3f43018f4 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -184,6 +184,118 @@ } } }, + "ceph": { + "properties": { + "health": { + "properties": { + "cluster": { + "properties": { + "overall_status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "timechecks": { + "properties": { + "epoch": { + "type": "long" + }, + "round": { + "properties": { + "status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "value": { + "type": "long" + } + } + } + } + } + } + }, + "mon": { + "properties": { + "available": { + "properties": { + "kb": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + }, + "health": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "last_updated": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "store_stats": { + "properties": { + "last_updated": { + "type": "long" + }, + "log": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "misc": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "sst": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "kb": { + "type": "long" + } + } + }, + "used": { + "properties": { + "kb": { + "type": "long" + } + } + } + } + } + } + } + } + }, "couchbase": { "properties": { "bucket": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 5d4b27fb515..09b912830bf 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -185,6 +185,114 @@ } } }, + "ceph": { + "properties": { + "health": { + "properties": { + "cluster": { + "properties": { + "overall_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "timechecks": { + "properties": { + "epoch": { + "type": "long" + }, + "round": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + } + } + } + } + } + }, + "mon": { + "properties": { + "available": { + "properties": { + "kb": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + }, + "health": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "store_stats": { + "properties": { + "last_updated": { + "type": "long" + }, + "log": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "misc": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "sst": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "kb": { + "type": "long" + } + } + }, + "used": { + "properties": { + "kb": { + "type": "long" + } + } + } + } + } + } + } + } + }, "couchbase": { "properties": { "bucket": { diff --git a/metricbeat/module/ceph/_meta/config.yml b/metricbeat/module/ceph/_meta/config.yml new file mode 100644 index 00000000000..f783c6e88a5 --- /dev/null +++ b/metricbeat/module/ceph/_meta/config.yml @@ -0,0 +1,5 @@ +#- module: ceph +# metricsets: ["health"] +# enabled: true +# period: 10s +# hosts: ["localhost:5000"] diff --git a/metricbeat/module/ceph/_meta/docs.asciidoc b/metricbeat/module/ceph/_meta/docs.asciidoc new file mode 100644 index 00000000000..20015a2ad96 --- /dev/null +++ b/metricbeat/module/ceph/_meta/docs.asciidoc @@ -0,0 +1,4 @@ +== ceph Module + +This is the ceph Module. Metrics are collected submitting HTTP GET requests to ceph-rest-api. +Reference: http://docs.ceph.com/docs/master/man/8/ceph-rest-api/ diff --git a/metricbeat/module/ceph/_meta/fields.yml b/metricbeat/module/ceph/_meta/fields.yml new file mode 100644 index 00000000000..71ee34a9755 --- /dev/null +++ b/metricbeat/module/ceph/_meta/fields.yml @@ -0,0 +1,12 @@ +- key: ceph + title: "ceph" + description: > + experimental[] + ceph Module + short_config: false + fields: + - name: ceph + type: group + description: > + `ceph` contains the metrics that were scraped from CEPH. + fields: diff --git a/metricbeat/module/ceph/doc.go b/metricbeat/module/ceph/doc.go new file mode 100644 index 00000000000..031b80e3cc6 --- /dev/null +++ b/metricbeat/module/ceph/doc.go @@ -0,0 +1,4 @@ +/* +Package ceph is a Metricbeat module that contains MetricSets. +*/ +package ceph diff --git a/metricbeat/module/ceph/health/_meta/data.json b/metricbeat/module/ceph/health/_meta/data.json new file mode 100644 index 00000000000..b251ac646b2 --- /dev/null +++ b/metricbeat/module/ceph/health/_meta/data.json @@ -0,0 +1,28 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "ceph": { + "health": { + "cluster": { + "overall_stats": "HEALTH_OK", + "timechecks": { + "epoch": 3, + "round": { + "status": "finished", + "value": 0 + } + } + } + } + }, + "metricset": { + "host": "172.17.0.1:5000", + "module": "ceph", + "name": "health", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/health/_meta/docs.asciidoc b/metricbeat/module/ceph/health/_meta/docs.asciidoc new file mode 100644 index 00000000000..739729d5f1f --- /dev/null +++ b/metricbeat/module/ceph/health/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== ceph health MetricSet + +This is the health metricset of the module ceph. diff --git a/metricbeat/module/ceph/health/_meta/fields.yml b/metricbeat/module/ceph/health/_meta/fields.yml new file mode 100644 index 00000000000..db52e92790f --- /dev/null +++ b/metricbeat/module/ceph/health/_meta/fields.yml @@ -0,0 +1,73 @@ +- name: health + type: group + description: > + health + fields: + - name: cluster.overall_status + type: keyword + description: > + Overall status of the cluster + - name: cluster.timechecks.epoch + type: long + description: > + Map version + - name: cluster.timechecks.round.value + type: long + description: > + timecheck round + - name: cluster.timechecks.round.status + type: keyword + description: > + Status of the round + - name: mon.available.pct + type: long + description: > + Available percent of the MON + - name: mon.health + type: keyword + description: > + Health of the MON + - name: mon.available.kb + type: long + description: > + Available KB of the MON + - name: mon.total.kb + type: long + description: > + Total KB of the MON + - name: mon.used.kb + type: long + description: > + Used KB of the MON + - name: mon.last_updated + type: date + description: > + Time when was updated + - name: mon.name + type: keyword + description: > + Name of the MON + - name: mon.store_stats.log.bytes + type: long + description: > + Log bytes of MON + format: bytes + - name: mon.store_stats.misc.bytes + type: long + description: > + Misc bytes of MON + format: bytes + - name: mon.store_stats.sst.bytes + type: long + description: > + SST bytes of MON + format: bytes + - name: mon.store_stats.total.bytes + type: long + description: > + Total bytes of MON + format: bytes + - name: mon.store_stats.last_updated + type: long + description: > + Last updated diff --git a/metricbeat/module/ceph/health/data.go b/metricbeat/module/ceph/health/data.go new file mode 100644 index 00000000000..e5b60bb1616 --- /dev/null +++ b/metricbeat/module/ceph/health/data.go @@ -0,0 +1,138 @@ +package health + +import ( + "encoding/json" + "io" + "time" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type Tick struct { + time.Time +} + +var format = "2006-01-02 15:04:05" + +func (t *Tick) MarshalJSON() ([]byte, error) { + return []byte(t.Time.Format(format)), nil +} + +func (t *Tick) UnmarshalJSON(b []byte) (err error) { + b = b[1 : len(b)-1] + t.Time, err = time.Parse(format, string(b)) + return +} + +type StoreStats struct { + BytesTotal int64 `json:"bytes_total"` + BytesLog int64 `json:"bytes_log"` + LastUpdated string `json:"last_updated"` + BytesMisc int64 `json:"bytes_misc"` + BytesSSt int64 `json:"bytes_sst"` +} + +type Mon struct { + LastUpdated Tick `json:"last_updated"` + Name string `json:"name"` + AvailPercent int64 `json:"avail_percent"` + KbTotal int64 `json:"kb_total"` + KbAvail int64 `json:"kb_avail"` + Health string `json:"health"` + KbUsed int64 `json:"kb_used"` + StoreStats StoreStats `json:"store_stats"` +} + +type HealthServices struct { + Mons []Mon `json:"mons"` +} + +type Health struct { + HealthServices []HealthServices `json:"health_services"` +} + +type Timecheck struct { + RoundStatus string `json:"round_status"` + Epoch int64 `json:"epoch"` + Round int64 `json:"round"` +} + +type Output struct { + OverallStatus string `json:"overall_status"` + Timechecks Timecheck `json:"timechecks"` + Health Health `json:"health"` +} + +type HealthRequest struct { + Status string `json:"status"` + Output Output `json:"output"` +} + +func eventsMapping(body io.Reader) []common.MapStr { + + var d HealthRequest + err := json.NewDecoder(body).Decode(&d) + if err != nil { + logp.Err("Error: ", err) + } + + events := []common.MapStr{} + + event := common.MapStr{ + "cluster": common.MapStr{ + "overall_stats": d.Output.OverallStatus, + "timechecks": common.MapStr{ + "epoch": d.Output.Timechecks.Epoch, + "round": common.MapStr{ + "value": d.Output.Timechecks.Round, + "status": d.Output.Timechecks.RoundStatus, + }, + }, + }, + } + + events = append(events, event) + + for _, HealthService := range d.Output.Health.HealthServices { + for _, Mon := range HealthService.Mons { + event := common.MapStr{ + "mon": common.MapStr{ + "last_updated": Mon.LastUpdated, + "name": Mon.Name, + "available": common.MapStr{ + "pct": Mon.AvailPercent, + "kb": Mon.KbAvail, + }, + "total": common.MapStr{ + "kb": Mon.KbTotal, + }, + "health": Mon.Health, + "used": common.MapStr{ + "kb": Mon.KbUsed, + }, + "store_stats": common.MapStr{ + "log": common.MapStr{ + "bytes": Mon.StoreStats.BytesLog, + }, + "misc": common.MapStr{ + "bytes": Mon.StoreStats.BytesMisc, + }, + "sst": common.MapStr{ + "bytes": Mon.StoreStats.BytesSSt, + }, + "total": common.MapStr{ + "bytes": Mon.StoreStats.BytesTotal, + }, + "last_updated": Mon.StoreStats.LastUpdated, + }, + }, + } + + events = append(events, event) + } + + } + + return events +} diff --git a/metricbeat/module/ceph/health/health.go b/metricbeat/module/ceph/health/health.go new file mode 100644 index 00000000000..f5e4aba8de6 --- /dev/null +++ b/metricbeat/module/ceph/health/health.go @@ -0,0 +1,62 @@ +package health + +import ( + "fmt" + "net/http" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/api/v0.1/health" +) + +var ( + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() +) + +func init() { + if err := mb.Registry.AddMetricSet("ceph", "health", New, hostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + client *http.Client +} + +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The ceph health metricset is experimental") + + return &MetricSet{ + BaseMetricSet: base, + client: &http.Client{Timeout: base.Module().Config().Timeout}, + }, nil +} + +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + + req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) + + req.Header.Set("Accept", "application/json") + + resp, err := m.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making http request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + } + + return eventsMapping(resp.Body), nil +} diff --git a/metricbeat/module/ceph/health/health_integration_test.go b/metricbeat/module/ceph/health/health_integration_test.go new file mode 100644 index 00000000000..3380739960a --- /dev/null +++ b/metricbeat/module/ceph/health/health_integration_test.go @@ -0,0 +1,47 @@ +package health + +import ( + "fmt" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + "os" + "testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"health"}, + "hosts": getTestCephHost(), + } +} + +const ( + cephDefaultHost = "172.17.0.1" + cephDefaultPort = "5000" +) + +func getTestCephHost() string { + return fmt.Sprintf("%v:%v", + getenv("CEPH_HOST", cephDefaultHost), + getenv("CEPH_PORT", cephDefaultPort), + ) +} + +func getenv(name, defaultValue string) string { + return strDefault(os.Getenv(name), defaultValue) +} + +func strDefault(a, defaults string) string { + if len(a) == 0 { + return defaults + } + return a +} diff --git a/metricbeat/module/ceph/health/health_test.go b/metricbeat/module/ceph/health/health_test.go new file mode 100644 index 00000000000..a3ea8c77608 --- /dev/null +++ b/metricbeat/module/ceph/health/health_test.go @@ -0,0 +1,89 @@ +package health + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/common" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestFetchEventContents(t *testing.T) { + absPath, err := filepath.Abs("./testdata/") + + response, err := ioutil.ReadFile(absPath + "/sample_response.json") + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "appication/json;") + w.Write([]byte(response)) + })) + defer server.Close() + + config := map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"health"}, + "hosts": []string{server.URL}, + } + + f := mbtest.NewEventsFetcher(t, config) + events, err := f.Fetch() + event := events[0] + if !assert.NoError(t, err) { + t.FailNow() + } + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + cluster := event["cluster"].(common.MapStr) + assert.EqualValues(t, "HEALTH_OK", cluster["overall_stats"]) + + timechecks := cluster["timechecks"].(common.MapStr) + assert.EqualValues(t, 3, timechecks["epoch"]) + + round := timechecks["round"].(common.MapStr) + assert.EqualValues(t, 0, round["value"]) + assert.EqualValues(t, "finished", round["status"]) + + event = events[1] + if !assert.NoError(t, err) { + t.FailNow() + } + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + mon := event["mon"].(common.MapStr) + assert.EqualValues(t, "HEALTH_OK", mon["health"]) + assert.EqualValues(t, "ceph", mon["name"]) + assert.EqualValues(t, "2017-01-19 11:34:50.700723 +0000 UTC", mon["last_updated"].(Tick).Time.String()) + + available := mon["available"].(common.MapStr) + assert.EqualValues(t, 4091244, available["kb"]) + assert.EqualValues(t, 65, available["pct"]) + + total := mon["total"].(common.MapStr) + assert.EqualValues(t, 6281216, total["kb"]) + + used := mon["used"].(common.MapStr) + assert.EqualValues(t, 2189972, used["kb"]) + + store_stats := mon["store_stats"].(common.MapStr) + assert.EqualValues(t, "0.000000", store_stats["last_updated"]) + + misc := store_stats["misc"].(common.MapStr) + assert.EqualValues(t, 840, misc["bytes"]) + + log := store_stats["log"].(common.MapStr) + assert.EqualValues(t, 8488103, log["bytes"]) + + sst := store_stats["sst"].(common.MapStr) + assert.EqualValues(t, 0, sst["bytes"]) + + total = store_stats["total"].(common.MapStr) + assert.EqualValues(t, 8488943, total["bytes"]) + +} diff --git a/metricbeat/module/ceph/health/testdata/sample_response.json b/metricbeat/module/ceph/health/testdata/sample_response.json new file mode 100644 index 00000000000..bfc55d8c109 --- /dev/null +++ b/metricbeat/module/ceph/health/testdata/sample_response.json @@ -0,0 +1,41 @@ +{ + "status":"OK", + "output":{ + "detail":[ + + ], + "timechecks":{ + "round_status":"finished", + "epoch":3, + "round":0 + }, + "health":{ + "health_services":[ + { + "mons":[ + { + "last_updated":"2017-01-19 11:34:50.700723", + "name":"ceph", + "avail_percent":65, + "kb_total":6281216, + "kb_avail":4091244, + "health":"HEALTH_OK", + "kb_used":2189972, + "store_stats":{ + "bytes_total":8488943, + "bytes_log":8488103, + "last_updated":"0.000000", + "bytes_misc":840, + "bytes_sst":0 + } + } + ] + } + ] + }, + "overall_status":"HEALTH_OK", + "summary":[ + + ] + } +} From b04384a3703c95a27725ba46608a9c3d6890f442 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Wed, 25 Jan 2017 09:39:12 +0100 Subject: [PATCH 15/78] Custom HTTP headers for the Elasticsearch output (#3400) Configuration looks like this: ``` output.elasticsearch.headers: X-My-Header: Contents of the header ``` To use from the CLI: ``` metricbeat -E "output.elasticsearch.headers.X-test=Test value" ``` It's not possible to set the same header name more than once with different values, but it is possible to separate header values with a comma, which has the same meaning as per the RFC. Closes #1768. --- CHANGELOG.asciidoc | 1 + filebeat/filebeat.full.yml | 4 +++ heartbeat/heartbeat.full.yml | 4 +++ libbeat/_meta/config.full.yml | 4 +++ libbeat/docs/outputconfig.asciidoc | 14 ++++++++ libbeat/outputs/elasticsearch/client.go | 8 +++++ libbeat/outputs/elasticsearch/client_test.go | 37 ++++++++++++++++++++ libbeat/outputs/elasticsearch/config.go | 1 + libbeat/outputs/elasticsearch/output.go | 2 ++ metricbeat/metricbeat.full.yml | 4 +++ packetbeat/packetbeat.full.yml | 4 +++ winlogbeat/winlogbeat.full.yml | 4 +++ 12 files changed, 87 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index edad6ec64b4..1bfa94b249a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -63,6 +63,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Added a NOTICE file containing the notices and licenses of the dependencies. {pull}3334[3334]. - Files created by Beats (logs, registry, file output) will have 0600 permissions. {pull}3387[3387]. - RPM/deb packages will now install the config file with 0600 permissions. {pull}3382[3382] +- Add the option to pass custom HTTP headers to the Elasticsearch output. {pull}3400[3400] *Metricbeat* diff --git a/filebeat/filebeat.full.yml b/filebeat/filebeat.full.yml index bd2e0ce5e57..fd6c2744366 100644 --- a/filebeat/filebeat.full.yml +++ b/filebeat/filebeat.full.yml @@ -355,6 +355,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 diff --git a/heartbeat/heartbeat.full.yml b/heartbeat/heartbeat.full.yml index 72576b16bf9..563b02ecdef 100644 --- a/heartbeat/heartbeat.full.yml +++ b/heartbeat/heartbeat.full.yml @@ -310,6 +310,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 diff --git a/libbeat/_meta/config.full.yml b/libbeat/_meta/config.full.yml index 56c922754d0..05087a79d4c 100644 --- a/libbeat/_meta/config.full.yml +++ b/libbeat/_meta/config.full.yml @@ -112,6 +112,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 diff --git a/libbeat/docs/outputconfig.asciidoc b/libbeat/docs/outputconfig.asciidoc index db208e9e0c3..4d8883ac188 100644 --- a/libbeat/docs/outputconfig.asciidoc +++ b/libbeat/docs/outputconfig.asciidoc @@ -135,6 +135,20 @@ An HTTP path prefix that is prepended to the HTTP API calls. This is useful for the cases where Elasticsearch listens behind an HTTP reverse proxy that exports the API under a custom prefix. +===== headers + +Custom HTTP headers to add to each request created by the Elasticsearch output. +Example: + +[source,yaml] +------------------------------------------------------------------------------ +output.elasticsearch.headers: + X-My-Header: Header contents +------------------------------------------------------------------------------ + +It is generally possible to specify multiple header values for the same header +name by separating them with a comma. + ===== proxy_url The URL of the proxy to use when connecting to the Elasticsearch servers. The diff --git a/libbeat/outputs/elasticsearch/client.go b/libbeat/outputs/elasticsearch/client.go index 2b2322af9a1..98ae1d45548 100644 --- a/libbeat/outputs/elasticsearch/client.go +++ b/libbeat/outputs/elasticsearch/client.go @@ -46,6 +46,7 @@ type ClientSettings struct { TLS *transport.TLSConfig Username, Password string Parameters map[string]string + Headers map[string]string Index outil.Selector Pipeline *outil.Selector Timeout time.Duration @@ -58,6 +59,7 @@ type Connection struct { URL string Username string Password string + Headers map[string]string http *http.Client onConnectCallback func() error @@ -160,6 +162,7 @@ func NewClient( URL: s.URL, Username: s.Username, Password: s.Password, + Headers: s.Headers, http: &http.Client{ Transport: &http.Transport{ Dial: dialer.Dial, @@ -208,6 +211,7 @@ func (client *Client) Clone() *Client { Username: client.Username, Password: client.Password, Parameters: nil, // XXX: do not pass params? + Headers: client.Headers, Timeout: client.http.Timeout, CompressionLevel: client.compressionLevel, }, @@ -700,6 +704,10 @@ func (conn *Connection) execHTTPRequest(req *http.Request) (int, []byte, error) req.SetBasicAuth(conn.Username, conn.Password) } + for name, value := range conn.Headers { + req.Header.Add(name, value) + } + resp, err := conn.http.Do(req) if err != nil { return 0, nil, err diff --git a/libbeat/outputs/elasticsearch/client_test.go b/libbeat/outputs/elasticsearch/client_test.go index 324d25ea638..5f879ebdb43 100644 --- a/libbeat/outputs/elasticsearch/client_test.go +++ b/libbeat/outputs/elasticsearch/client_test.go @@ -4,6 +4,8 @@ package elasticsearch import ( "fmt" + "net/http" + "net/http/httptest" "strings" "testing" "time" @@ -271,3 +273,38 @@ func BenchmarkCollectPublishFailAll(b *testing.B) { } } } + +func TestClientWithHeaders(t *testing.T) { + requestCount := 0 + // start a mock HTTP server + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "testing value", r.Header.Get("X-Test")) + requestCount += 1 + fmt.Fprintln(w, "Hello, client") + })) + defer ts.Close() + + client, err := NewClient(ClientSettings{ + URL: ts.URL, + Index: outil.MakeSelector(outil.ConstSelectorExpr("test")), + Headers: map[string]string{ + "X-Test": "testing value", + }, + }, nil) + assert.NoError(t, err) + + // simple ping + client.Ping(1 * time.Second) + assert.Equal(t, 1, requestCount) + + // bulk request + event := outputs.Data{Event: common.MapStr{ + "@timestamp": common.Time(time.Now()), + "type": "libbeat", + "message": "Test message from libbeat", + }} + events := []outputs.Data{event, event, event} + _, err = client.PublishEvents(events) + assert.NoError(t, err) + assert.Equal(t, 2, requestCount) +} diff --git a/libbeat/outputs/elasticsearch/config.go b/libbeat/outputs/elasticsearch/config.go index 6d16479ea60..c30f68702db 100644 --- a/libbeat/outputs/elasticsearch/config.go +++ b/libbeat/outputs/elasticsearch/config.go @@ -10,6 +10,7 @@ type elasticsearchConfig struct { Protocol string `config:"protocol"` Path string `config:"path"` Params map[string]string `config:"parameters"` + Headers map[string]string `config:"headers"` Username string `config:"username"` Password string `config:"password"` ProxyURL string `config:"proxy_url"` diff --git a/libbeat/outputs/elasticsearch/output.go b/libbeat/outputs/elasticsearch/output.go index 4ab3b03ca15..20e7e1fe62a 100644 --- a/libbeat/outputs/elasticsearch/output.go +++ b/libbeat/outputs/elasticsearch/output.go @@ -144,6 +144,7 @@ func NewElasticsearchClients(cfg *common.Config) ([]Client, error) { Username: config.Username, Password: config.Password, Parameters: params, + Headers: config.Headers, Timeout: config.Timeout, CompressionLevel: config.CompressionLevel, }, nil) @@ -368,6 +369,7 @@ func makeClientFactory( Username: config.Username, Password: config.Password, Parameters: params, + Headers: config.Headers, Timeout: config.Timeout, CompressionLevel: config.CompressionLevel, }, onConnected) diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 11109809a3d..0255ac4d040 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -406,6 +406,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 diff --git a/packetbeat/packetbeat.full.yml b/packetbeat/packetbeat.full.yml index fe992c0bd18..6e7f6e29ae0 100644 --- a/packetbeat/packetbeat.full.yml +++ b/packetbeat/packetbeat.full.yml @@ -566,6 +566,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 diff --git a/winlogbeat/winlogbeat.full.yml b/winlogbeat/winlogbeat.full.yml index 19919a15167..38b9bd24e9f 100644 --- a/winlogbeat/winlogbeat.full.yml +++ b/winlogbeat/winlogbeat.full.yml @@ -147,6 +147,10 @@ output.elasticsearch: # Optional HTTP Path #path: "/elasticsearch" + # Custom HTTP headers to add to each request + #headers: + # X-My-Header: Contents of the header + # Proxy server url #proxy_url: http://proxy:3128 From a8295a27c11c94b734d46bd814da27a294f0ca42 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Wed, 25 Jan 2017 04:06:07 -0500 Subject: [PATCH 16/78] Modify fix-permissions to look at entire repo (#3453) The list of files that are owned by root after running the build in docker extends outside of the build dir so just check everything in the repo. These are the files that this change will fix. ``` ./filebeat/tests/system/test_load.pyc ./filebeat/tests/system/test_harvester.pyc ./filebeat/tests/system/filebeat.pyc ./filebeat/tests/system/test_fields.pyc ./filebeat/tests/system/test_multiline.pyc ./filebeat/tests/system/test_shutdown.pyc ./filebeat/tests/system/test_processors.pyc ./filebeat/tests/system/test_registrar.pyc ./filebeat/tests/system/test_modules.pyc ./filebeat/tests/system/test_publisher.pyc ./filebeat/tests/system/test_prospector.pyc ./filebeat/tests/system/test_reload.pyc ./filebeat/tests/system/test_json.pyc ./filebeat/tests/system/test_crawler.pyc ./filebeat/tests/system/test_migration.pyc ./filebeat/tests/files/logs/nasa-50k.log ./filebeat/data find: `./filebeat/data': Permission denied ./filebeat/filebeat.test ./metricbeat/tests/system/test_kafka.pyc ./metricbeat/tests/system/test_base.pyc ./metricbeat/tests/system/metricbeat.pyc ./metricbeat/tests/system/test_docker.pyc ./metricbeat/tests/system/test_processors.pyc ./metricbeat/tests/system/test_mongodb.pyc ./metricbeat/tests/system/test_haproxy.pyc ./metricbeat/tests/system/test_postgresql.pyc ./metricbeat/tests/system/test_config.pyc ./metricbeat/tests/system/test_zookeeper.pyc ./metricbeat/tests/system/test_redis.pyc ./metricbeat/tests/system/test_reload.pyc ./metricbeat/tests/system/test_prometheus.pyc ./metricbeat/tests/system/test_system.pyc ./metricbeat/tests/system/test_apache.pyc ./metricbeat/tests/system/test_mysql.pyc ./metricbeat/metricbeat.test ./metricbeat/data find: `./metricbeat/data': Permission denied ./libbeat/dashboards/import_dashboards ./libbeat/tests/system/test_base.pyc ./libbeat/tests/system/beat/beat.pyc ./libbeat/tests/system/beat/__init__.pyc ./libbeat/tests/system/base.pyc ./libbeat/tests/system/test_dashboard.pyc ./libbeat/libbeat.test ./libbeat/data find: `./libbeat/data': Permission denied ``` --- libbeat/scripts/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index 5039733f0f3..825526c1426 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -161,7 +161,7 @@ fast-system-tests: ${BEAT_NAME}.test python-env # Run benchmark tests .PHONY: benchmark-tests -benchmark-tests: ## @testing Runs bechmarks (NOT YET IMPLEMENTED) +benchmark-tests: ## @testing Runs benchmarks (NOT YET IMPLEMENTED) # No benchmark tests exist so far #go test -bench=. ${GOPACKAGES} @@ -195,7 +195,7 @@ testsuite: clean update $(MAKE) unit-tests # Setups environment if TEST_ENVIRONMENT is set to true - # Only runs integration tests with test environemtn + # Only runs integration tests with test environment if [ $(TEST_ENVIRONMENT) = true ]; then \ $(MAKE) integration-tests-environment; \ fi @@ -434,7 +434,7 @@ package-dashboards: package-setup fix-permissions: # Change ownership of all files inside /build folder from root/root to current user/group - docker run -v ${BUILD_DIR}:/build alpine:3.4 sh -c "chown -R $(shell id -u):$(shell id -g) /build" + docker run -v ${PWD}:/beat alpine:3.4 sh -c "find /beat -user 0 -exec chown $(shell id -u):$(shell id -g) {} \;" set_version: ## @packaging VERSION=x.y.z set the version of the beat to x.y.z ${ES_BEATS}/dev-tools/set_version ${VERSION} From b8a49d19165b945558ddbfab8f8b4f6cd448f78b Mon Sep 17 00:00:00 2001 From: sebastienmusso Date: Wed, 25 Jan 2017 11:37:55 +0100 Subject: [PATCH 17/78] Add HealthCheck information for Metricbeat docker module (#3357) --- metricbeat/_meta/beat.full.yml | 2 +- metricbeat/docs/fields.asciidoc | 62 +++++++++++++++++++ metricbeat/docs/modules/docker.asciidoc | 6 +- .../docs/modules/docker/healthcheck.asciidoc | 19 ++++++ metricbeat/include/list.go | 1 + metricbeat/metricbeat.full.yml | 2 +- metricbeat/metricbeat.template-es2x.json | 30 +++++++++ metricbeat/metricbeat.template.json | 28 +++++++++ metricbeat/module/docker/_meta/config.yml | 2 +- .../module/docker/container/_meta/data.json | 2 +- metricbeat/module/docker/container/data.go | 1 + .../module/docker/healthcheck/_meta/data.json | 26 ++++++++ .../docker/healthcheck/_meta/docs.asciidoc | 4 ++ .../docker/healthcheck/_meta/fields.yml | 34 ++++++++++ .../healthcheck/container_integration_test.go | 25 ++++++++ metricbeat/module/docker/healthcheck/data.go | 52 ++++++++++++++++ .../module/docker/healthcheck/healthcheck.go | 52 ++++++++++++++++ 17 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 metricbeat/docs/modules/docker/healthcheck.asciidoc create mode 100644 metricbeat/module/docker/healthcheck/_meta/data.json create mode 100644 metricbeat/module/docker/healthcheck/_meta/docs.asciidoc create mode 100644 metricbeat/module/docker/healthcheck/_meta/fields.yml create mode 100644 metricbeat/module/docker/healthcheck/container_integration_test.go create mode 100644 metricbeat/module/docker/healthcheck/data.go create mode 100644 metricbeat/module/docker/healthcheck/healthcheck.go diff --git a/metricbeat/_meta/beat.full.yml b/metricbeat/_meta/beat.full.yml index 957fadb0336..8456c095de1 100644 --- a/metricbeat/_meta/beat.full.yml +++ b/metricbeat/_meta/beat.full.yml @@ -110,7 +110,7 @@ metricbeat.modules: #------------------------------- Docker Module ------------------------------- #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index bdc9f50023c..c48fbb59e8a 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1330,6 +1330,68 @@ type: scaled_float Number of reads and writes combined. +[float] +== healthcheck Fields + +Docker container metrics. + + + +[float] +=== docker.healthcheck.failingstreak + +type: integer + +concurent failed check + + +[float] +=== docker.healthcheck.status + +type: keyword + +Healthcheck status code + + +[float] +== event Fields + +event fields. + + + +[float] +=== docker.healthcheck.event.end_date + +type: date + +Healthcheck end date + + +[float] +=== docker.healthcheck.event.start_date + +type: date + +Healthcheck start date + + +[float] +=== docker.healthcheck.event.output + +type: keyword + +Healthcheck output + + +[float] +=== docker.healthcheck.event.exit_code + +type: integer + +Healthcheck status code + + [float] == info Fields diff --git a/metricbeat/docs/modules/docker.asciidoc b/metricbeat/docs/modules/docker.asciidoc index e743c67e703..2388f6ea9e0 100644 --- a/metricbeat/docs/modules/docker.asciidoc +++ b/metricbeat/docs/modules/docker.asciidoc @@ -21,7 +21,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s @@ -44,6 +44,8 @@ The following metricsets are available: * <> +* <> + * <> * <> @@ -56,6 +58,8 @@ include::docker/cpu.asciidoc[] include::docker/diskio.asciidoc[] +include::docker/healthcheck.asciidoc[] + include::docker/info.asciidoc[] include::docker/memory.asciidoc[] diff --git a/metricbeat/docs/modules/docker/healthcheck.asciidoc b/metricbeat/docs/modules/docker/healthcheck.asciidoc new file mode 100644 index 00000000000..220ba07630d --- /dev/null +++ b/metricbeat/docs/modules/docker/healthcheck.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-docker-healthcheck]] +include::../../../module/docker/healthcheck/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/docker/healthcheck/_meta/data.json[] +---- diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index 810ce066367..c14b4487708 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -20,6 +20,7 @@ import ( _ "github.com/elastic/beats/metricbeat/module/docker/container" _ "github.com/elastic/beats/metricbeat/module/docker/cpu" _ "github.com/elastic/beats/metricbeat/module/docker/diskio" + _ "github.com/elastic/beats/metricbeat/module/docker/healthcheck" _ "github.com/elastic/beats/metricbeat/module/docker/info" _ "github.com/elastic/beats/metricbeat/module/docker/memory" _ "github.com/elastic/beats/metricbeat/module/docker/network" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 0255ac4d040..d2e5899e121 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -110,7 +110,7 @@ metricbeat.modules: #------------------------------- Docker Module ------------------------------- #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 8d3f43018f4..9edd9d22715 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -780,6 +780,36 @@ } } }, + "healthcheck": { + "properties": { + "event": { + "properties": { + "end_date": { + "type": "date" + }, + "exit_code": { + "type": "long" + }, + "output": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "start_date": { + "type": "date" + } + } + }, + "failingstreak": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 09b912830bf..dff961ab1ba 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -778,6 +778,34 @@ } } }, + "healthcheck": { + "properties": { + "event": { + "properties": { + "end_date": { + "type": "date" + }, + "exit_code": { + "type": "long" + }, + "output": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_date": { + "type": "date" + } + } + }, + "failingstreak": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/module/docker/_meta/config.yml b/metricbeat/module/docker/_meta/config.yml index 745c6c91e59..f2440a78afd 100644 --- a/metricbeat/module/docker/_meta/config.yml +++ b/metricbeat/module/docker/_meta/config.yml @@ -1,5 +1,5 @@ #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/module/docker/container/_meta/data.json b/metricbeat/module/docker/container/_meta/data.json index 83ca1c4849d..5fec882399e 100644 --- a/metricbeat/module/docker/container/_meta/data.json +++ b/metricbeat/module/docker/container/_meta/data.json @@ -32,4 +32,4 @@ "rtt": 115 }, "type": "metricsets" -} \ No newline at end of file +} diff --git a/metricbeat/module/docker/container/data.go b/metricbeat/module/docker/container/data.go index 86d4d17dd87..199cb4df2ae 100644 --- a/metricbeat/module/docker/container/data.go +++ b/metricbeat/module/docker/container/data.go @@ -32,6 +32,7 @@ func eventMapping(cont *dc.APIContainers) common.MapStr { } labels := docker.DeDotLabels(cont.Labels) + if len(labels) > 0 { event["labels"] = labels } diff --git a/metricbeat/module/docker/healthcheck/_meta/data.json b/metricbeat/module/docker/healthcheck/_meta/data.json new file mode 100644 index 00000000000..2b33980faa2 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/data.json @@ -0,0 +1,26 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "docker": { + "healthcheck": { + "failingstreak": 0, + "status": "healthy", + "event": { + "end_date": "2017-01-09T20:38:13.080472813+01:00", + "exit_code": 0, + "output": "this is an event output", + "start_date": "2017-01-09T20:38:12.999970865+01:00", + } + } + }, + "metricset": { + "host": "/var/run/docker.sock", + "module": "docker", + "name": "container", + "rtt": 115 + }, + "type": "metricsets" +} diff --git a/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc b/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc new file mode 100644 index 00000000000..bfcf644a106 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc @@ -0,0 +1,4 @@ +=== Docker healthcheck Metricset + +The Docker `healthcheck` metricset collects information and statistics about +running Docker containers. diff --git a/metricbeat/module/docker/healthcheck/_meta/fields.yml b/metricbeat/module/docker/healthcheck/_meta/fields.yml new file mode 100644 index 00000000000..0b37d42f13b --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/fields.yml @@ -0,0 +1,34 @@ +- name: healthcheck + type: group + description: > + Docker container metrics. + fields: + - name: failingstreak + type: integer + description: > + concurent failed check + - name: status + type: keyword + description: > + Healthcheck status code + - name: event + type: group + description: > + event fields. + fields: + - name: end_date + type: date + description: > + Healthcheck end date + - name: start_date + type: date + description: > + Healthcheck start date + - name: output + type: keyword + description: > + Healthcheck output + - name: exit_code + type: integer + description: > + Healthcheck status code diff --git a/metricbeat/module/docker/healthcheck/container_integration_test.go b/metricbeat/module/docker/healthcheck/container_integration_test.go new file mode 100644 index 00000000000..8c1356f7366 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/container_integration_test.go @@ -0,0 +1,25 @@ +// +build integration + +package healthcheck + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "docker", + "metricsets": []string{"healthcheck"}, + "hosts": []string{"unix:///var/run/docker.sock"}, + } +} diff --git a/metricbeat/module/docker/healthcheck/data.go b/metricbeat/module/docker/healthcheck/data.go new file mode 100644 index 00000000000..fedce1739c9 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/data.go @@ -0,0 +1,52 @@ +package healthcheck + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/module/docker" + + dc "github.com/fsouza/go-dockerclient" + "strings" +) + +func eventsMapping(containersList []dc.APIContainers, m *MetricSet) []common.MapStr { + myEvents := []common.MapStr{} + for _, container := range containersList { + returnevent := eventMapping(&container, m) + // Compare event to empty event + if returnevent != nil { + myEvents = append(myEvents, returnevent) + } + } + return myEvents +} + +func eventMapping(cont *dc.APIContainers, m *MetricSet) common.MapStr { + event := common.MapStr{} + // Detect if healthcheck is available for container + if strings.Contains(cont.Status, "(") && strings.Contains(cont.Status, ")") { + container, _ := m.dockerClient.InspectContainer(cont.ID) + last_event := len(container.State.Health.Log) - 1 + // Detect if an healthcheck already occured + if last_event >= 0 { + event = common.MapStr{ + mb.ModuleData: common.MapStr{ + "container": common.MapStr{ + "name": docker.ExtractContainerName(cont.Names), + "id": cont.ID, + }, + }, + "status": container.State.Health.Status, + "failingstreak": container.State.Health.FailingStreak, + "event": common.MapStr{ + "start_date": common.Time(container.State.Health.Log[last_event].Start), + "end_date": common.Time(container.State.Health.Log[last_event].End), + "exit_code": container.State.Health.Log[last_event].ExitCode, + "output": container.State.Health.Log[last_event].Output, + }, + } + return event + } + } + return nil +} diff --git a/metricbeat/module/docker/healthcheck/healthcheck.go b/metricbeat/module/docker/healthcheck/healthcheck.go new file mode 100644 index 00000000000..f487e2708bb --- /dev/null +++ b/metricbeat/module/docker/healthcheck/healthcheck.go @@ -0,0 +1,52 @@ +package healthcheck + +import ( + dc "github.com/fsouza/go-dockerclient" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/module/docker" +) + +func init() { + if err := mb.Registry.AddMetricSet("docker", "healthcheck", New, docker.HostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + dockerClient *dc.Client +} + +// New creates a new instance of the docker healthcheck MetricSet. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The docker healthcheck metricset is experimental") + + config := docker.Config{} + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + client, err := docker.NewDockerClient(base.HostData().URI, config) + if err != nil { + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + dockerClient: client, + }, nil +} + +// Fetch returns a list of all containers as events. +// This is based on https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/list-containers. +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + // Fetch a list of all containers. + containers, err := m.dockerClient.ListContainers(dc.ListContainersOptions{}) + if err != nil { + return nil, err + } + return eventsMapping(containers, m), nil +} From ccb977d08844afac543952e8f22ae537c4c29f38 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 25 Jan 2017 12:23:03 +0100 Subject: [PATCH 18/78] Add -s to gofmt to also simplify code (#3461) Affected changes added --- filebeat/fileset/modules_integration_test.go | 2 +- filebeat/fileset/modules_test.go | 28 +++++++++---------- .../prospector/prospector_log_other_test.go | 20 ++++++------- libbeat/scripts/Makefile | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index 2a6167ea802..129ef897666 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -42,7 +42,7 @@ func TestSetupNginx(t *testing.T) { assert.NoError(t, err) configs := []ModuleConfig{ - ModuleConfig{Module: "nginx"}, + {Module: "nginx"}, } reg, err := newModuleRegistry(modulesPath, configs, nil) diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index b44a93d427e..5b36947d67c 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -24,9 +24,9 @@ func TestNewModuleRegistry(t *testing.T) { assert.NoError(t, err) configs := []ModuleConfig{ - ModuleConfig{Module: "nginx"}, - ModuleConfig{Module: "mysql"}, - ModuleConfig{Module: "syslog"}, + {Module: "nginx"}, + {Module: "mysql"}, + {Module: "syslog"}, } reg, err := newModuleRegistry(modulesPath, configs, nil) @@ -34,9 +34,9 @@ func TestNewModuleRegistry(t *testing.T) { assert.NotNil(t, reg) expectedModules := map[string][]string{ - "nginx": []string{"access", "error"}, - "mysql": []string{"slowlog", "error"}, - "syslog": []string{"system"}, + "nginx": {"access", "error"}, + "mysql": {"slowlog", "error"}, + "syslog": {"system"}, } assert.Equal(t, len(expectedModules), len(reg.registry)) @@ -69,12 +69,12 @@ func TestNewModuleRegistryConfig(t *testing.T) { { Module: "nginx", Filesets: map[string]*FilesetConfig{ - "access": &FilesetConfig{ + "access": { Var: map[string]interface{}{ "paths": []interface{}{"/hello/test"}, }, }, - "error": &FilesetConfig{ + "error": { Enabled: &falseVar, }, }, @@ -207,7 +207,7 @@ func TestAppendWithoutDuplicates(t *testing.T) { { Module: "moduleB", Filesets: map[string]*FilesetConfig{ - "fileset": &FilesetConfig{ + "fileset": { Var: map[string]interface{}{ "paths": "test", }, @@ -220,7 +220,7 @@ func TestAppendWithoutDuplicates(t *testing.T) { { Module: "moduleB", Filesets: map[string]*FilesetConfig{ - "fileset": &FilesetConfig{ + "fileset": { Var: map[string]interface{}{ "paths": "test", }, @@ -238,7 +238,7 @@ func TestAppendWithoutDuplicates(t *testing.T) { Module: "moduleB", Enabled: &falseVar, Filesets: map[string]*FilesetConfig{ - "fileset": &FilesetConfig{ + "fileset": { Var: map[string]interface{}{ "paths": "test", }, @@ -252,7 +252,7 @@ func TestAppendWithoutDuplicates(t *testing.T) { Module: "moduleB", Enabled: &falseVar, Filesets: map[string]*FilesetConfig{ - "fileset": &FilesetConfig{ + "fileset": { Var: map[string]interface{}{ "paths": "test", }, @@ -289,7 +289,7 @@ func TestMcfgFromConfig(t *testing.T) { expected: ModuleConfig{ Module: "nginx", Filesets: map[string]*FilesetConfig{ - "error": &FilesetConfig{ + "error": { Enabled: &falseVar, }, }, @@ -304,7 +304,7 @@ func TestMcfgFromConfig(t *testing.T) { expected: ModuleConfig{ Module: "nginx", Filesets: map[string]*FilesetConfig{ - "access": &FilesetConfig{ + "access": { Var: map[string]interface{}{ "test": false, }, diff --git a/filebeat/prospector/prospector_log_other_test.go b/filebeat/prospector/prospector_log_other_test.go index 8108968576e..2dd3bba582b 100644 --- a/filebeat/prospector/prospector_log_other_test.go +++ b/filebeat/prospector/prospector_log_other_test.go @@ -78,46 +78,46 @@ var initStateTests = []struct { }{ { []file.State{ - file.State{Source: "test"}, + {Source: "test"}, }, []string{"test"}, 1, }, { []file.State{ - file.State{Source: "notest"}, + {Source: "notest"}, }, []string{"test"}, 0, }, { []file.State{ - file.State{Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, - file.State{Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, + {Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, + {Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, }, []string{"*.log"}, 2, }, { []file.State{ - file.State{Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, - file.State{Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, + {Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, + {Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, }, []string{"test1.log"}, 1, }, { []file.State{ - file.State{Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, - file.State{Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, + {Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, + {Source: "test2.log", FileStateOS: file.StateOS{Inode: 2}}, }, []string{"test.log"}, 0, }, { []file.State{ - file.State{Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, - file.State{Source: "test2.log", FileStateOS: file.StateOS{Inode: 1}}, + {Source: "test1.log", FileStateOS: file.StateOS{Inode: 1}}, + {Source: "test2.log", FileStateOS: file.StateOS{Inode: 1}}, }, []string{"*.log"}, 1, // Expecting only 1 state because of some inode (this is only a theoretical case) diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index 825526c1426..4dd1b302850 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -92,7 +92,7 @@ check: ## @build Checks project and source code if everything is according to s .PHONY: fmt fmt: ## @build Runs gofmt -w on the project's source code, modifying any files that do not match its style. - gofmt -l -w ${GOFILES_NOVENDOR} + gofmt -s -l -w ${GOFILES_NOVENDOR} .PHONY: simplify simplify: ## @build Runs gofmt -s -w on the project's source code, modifying any files that do not match its style. From 5cf6aa8b104809bc3055c84f1dc28a7ff5117e10 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 25 Jan 2017 12:25:43 +0100 Subject: [PATCH 19/78] Improve PHP-PFM module (#3450) * Add docker environment for integration and system testing * Add system test file to check for correct docs. * Brings docs in line with generated output * Update data.json * Remove hostname fields as already part of metricset * Apply schema instead of manual conversion * Rename pool.pool to pool.name * Remove separate http client as not needed anymore This is a follow up PR for https://github.com/elastic/beats/pull/3415 --- metricbeat/docker-compose.yml | 8 ++- metricbeat/docker-entrypoint.sh | 2 + metricbeat/docs/fields.asciidoc | 2 +- metricbeat/metricbeat.template-es2x.json | 2 +- metricbeat/metricbeat.template.json | 2 +- metricbeat/module/php_fpm/_meta/Dockerfile | 2 + metricbeat/module/php_fpm/php_fpm.go | 59 ------------------- metricbeat/module/php_fpm/php_fpm_test.go | 30 ---------- .../module/php_fpm/pool/_meta/data.json | 51 ++++++++-------- .../module/php_fpm/pool/_meta/fields.yml | 2 +- metricbeat/module/php_fpm/pool/data.go | 35 ++++++----- metricbeat/module/php_fpm/pool/pool.go | 59 ++++++++----------- .../php_fpm/pool/pool_integration_test.go | 21 ++++++- metricbeat/tests/system/test_phpfpm.py | 38 ++++++++++++ 14 files changed, 143 insertions(+), 170 deletions(-) delete mode 100644 metricbeat/module/php_fpm/php_fpm.go delete mode 100644 metricbeat/module/php_fpm/php_fpm_test.go create mode 100644 metricbeat/tests/system/test_phpfpm.py diff --git a/metricbeat/docker-compose.yml b/metricbeat/docker-compose.yml index 6fac45b9451..085ec954d13 100644 --- a/metricbeat/docker-compose.yml +++ b/metricbeat/docker-compose.yml @@ -6,11 +6,12 @@ services: - apache - ceph - couchbase - - mongodb - haproxy - kafka + - mongodb - mysql - nginx + - phpfpm - postgresql - prometheus - redis @@ -36,6 +37,8 @@ services: - MYSQL_DSN=root:test@tcp(mysql:3306)/ - MYSQL_HOST=mysql - MYSQL_PORT=3306 + - PHPFPM_HOST=phpfpm + - PHPFPM_PORT=81 - POSTGRESQL_DSN=postgres://postgresql:5432?sslmode=disable - POSTGRESQL_HOST=postgresql - POSTGRESQL_PORT=5432 @@ -90,6 +93,9 @@ services: haproxy: build: ${PWD}/module/haproxy/_meta + phpfpm: + build: ${PWD}/module/php_fpm/_meta + postgresql: image: postgres:9.5.3 diff --git a/metricbeat/docker-entrypoint.sh b/metricbeat/docker-entrypoint.sh index d5a78b62534..0b0cee271ea 100755 --- a/metricbeat/docker-entrypoint.sh +++ b/metricbeat/docker-entrypoint.sh @@ -27,8 +27,10 @@ waitFor ${CEPH_HOST} ${CEPH_PORT} Ceph waitFor ${COUCHBASE_HOST} ${COUCHBASE_PORT} Couchbase waitFor ${HAPROXY_HOST} ${HAPROXY_PORT} HAProxy waitFor ${KAFKA_HOST} ${KAFKA_PORT} Kafka +waitFor ${MONGODB_HOST} ${MONGODB_PORT} MongoDB waitFor ${MYSQL_HOST} ${MYSQL_PORT} MySQL waitFor ${NGINX_HOST} ${NGINX_PORT} Nginx +waitFor ${PHPFPM_HOST} ${PHPFPM_PORT} PHP_FPM waitFor ${POSTGRESQL_HOST} ${POSTGRESQL_PORT} Postgresql waitFor ${PROMETHEUS_HOST} ${PROMETHEUS_PORT} Prometheus waitFor ${REDIS_HOST} ${REDIS_PORT} Redis diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index c48fbb59e8a..7eb7d5a5bb3 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -4046,7 +4046,7 @@ experimental[] [float] -=== php_fpm.pool.pool +=== php_fpm.pool.name type: keyword diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 9edd9d22715..2eb265d1386 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -2324,7 +2324,7 @@ } } }, - "pool": { + "name": { "ignore_above": 1024, "index": "not_analyzed", "type": "string" diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index dff961ab1ba..f4da00f229a 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -2297,7 +2297,7 @@ } } }, - "pool": { + "name": { "ignore_above": 1024, "type": "keyword" }, diff --git a/metricbeat/module/php_fpm/_meta/Dockerfile b/metricbeat/module/php_fpm/_meta/Dockerfile index 7e70cdb2b8a..783a3db96dc 100644 --- a/metricbeat/module/php_fpm/_meta/Dockerfile +++ b/metricbeat/module/php_fpm/_meta/Dockerfile @@ -2,3 +2,5 @@ FROM richarvey/nginx-php-fpm RUN echo "pm.status_path = /status" >> /etc/php7/php-fpm.d/www.conf ADD ./php-fpm.conf /etc/nginx/sites-enabled + +EXPOSE 81 diff --git a/metricbeat/module/php_fpm/php_fpm.go b/metricbeat/module/php_fpm/php_fpm.go deleted file mode 100644 index e20d352e3db..00000000000 --- a/metricbeat/module/php_fpm/php_fpm.go +++ /dev/null @@ -1,59 +0,0 @@ -package php_fpm - -import ( - "fmt" - "io" - "net/http" - - "github.com/elastic/beats/metricbeat/mb" - "github.com/elastic/beats/metricbeat/mb/parse" -) - -const ( - defaultScheme = "http" - defaultPath = "/status" -) - -// HostParser is used for parsing the configured php-fpm hosts. -var HostParser = parse.URLHostParserBuilder{ - DefaultScheme: defaultScheme, - DefaultPath: defaultPath, - QueryParams: "json", - PathConfigKey: "status_path", -}.Build() - -// StatsClient provides access to php-fpm stats api -type StatsClient struct { - address string - user string - password string - http *http.Client -} - -// NewStatsClient creates a new StatsClient -func NewStatsClient(m mb.BaseMetricSet) *StatsClient { - return &StatsClient{ - address: m.HostData().SanitizedURI, - user: m.HostData().User, - password: m.HostData().Password, - http: &http.Client{Timeout: m.Module().Config().Timeout}, - } -} - -// Fetch php-fpm stats -func (c *StatsClient) Fetch() (io.ReadCloser, error) { - req, err := http.NewRequest("GET", c.address, nil) - if c.user != "" || c.password != "" { - req.SetBasicAuth(c.user, c.password) - } - resp, err := c.http.Do(req) - if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) - } - - return resp.Body, nil -} diff --git a/metricbeat/module/php_fpm/php_fpm_test.go b/metricbeat/module/php_fpm/php_fpm_test.go deleted file mode 100644 index e5e6fe1342d..00000000000 --- a/metricbeat/module/php_fpm/php_fpm_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package php_fpm - -import ( - "testing" - - mbtest "github.com/elastic/beats/metricbeat/mb/testing" - - "github.com/stretchr/testify/assert" -) - -func TestHostParser(t *testing.T) { - tests := []struct { - host, expected string - }{ - {"localhost", "http://localhost/status?json="}, - {"localhost:123", "http://localhost:123/status?json="}, - {"http://localhost:123", "http://localhost:123/status?json="}, - } - - m := mbtest.NewTestModule(t, map[string]interface{}{}) - - for _, test := range tests { - hi, err := HostParser(m, test.host) - if err != nil { - t.Error("failed on", test.host, err) - continue - } - assert.Equal(t, test.expected, hi.URI) - } -} diff --git a/metricbeat/module/php_fpm/pool/_meta/data.json b/metricbeat/module/php_fpm/pool/_meta/data.json index 06cb2852633..8616a772903 100644 --- a/metricbeat/module/php_fpm/pool/_meta/data.json +++ b/metricbeat/module/php_fpm/pool/_meta/data.json @@ -1,25 +1,28 @@ { - "@timestamp": "2017-01-18T23:57:23.960Z", - "beat": { - "hostname": "host.example.com", - "name": "host.example.com" - }, - "metricset": { - "host": "localhost:8081", - "module": "php_fpm", - "name": "pool", - "rtt": 1237 - }, - "php_fpm": { - "pool": { - "connections.accepted": 803, - "connections.queued": 0, - "hostname": "localhost:8081", - "pool": "www", - "processes.active": 1, - "processes.idle": 2, - "requests.slow": 0 - } - }, - "type": "metricsets" -} + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "metricset": { + "host": "127.0.0.1:81", + "module": "php_fpm", + "name": "pool", + "rtt": 115 + }, + "php_fpm": { + "pool": { + "connections": { + "accepted": 13, + "queued": 0 + }, + "pool": "www", + "processes": { + "active": 1, + "idle": 2 + }, + "slow_requests": 0 + } + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/php_fpm/pool/_meta/fields.yml b/metricbeat/module/php_fpm/pool/_meta/fields.yml index ba22858f91e..c4e2a096b0b 100644 --- a/metricbeat/module/php_fpm/pool/_meta/fields.yml +++ b/metricbeat/module/php_fpm/pool/_meta/fields.yml @@ -4,7 +4,7 @@ `pool` contains the metrics that were obtained from the PHP-FPM process pool. fields: - - name: pool + - name: name type: keyword description: > The name of the pool. diff --git a/metricbeat/module/php_fpm/pool/data.go b/metricbeat/module/php_fpm/pool/data.go index 48a797f36cf..7c6b1d88895 100644 --- a/metricbeat/module/php_fpm/pool/data.go +++ b/metricbeat/module/php_fpm/pool/data.go @@ -1,18 +1,21 @@ package pool -type poolStats struct { - Pool string `json:"pool"` - ProcessManager string `json:"process manager"` - StartTime int `json:"start time"` - StartSince int `json:"start since"` - AcceptedConn int `json:"accepted conn"` - ListenQueue int `json:"listen queue"` - MaxListenQueue int `json:"max listen queue"` - ListenQueueLen int `json:"listen queue len"` - IdleProcesses int `json:"idle processes"` - ActiveProcesses int `json:"active processes"` - TotalProcesses int `json:"total processes"` - MaxActiveProcesses int `json:"max active processes"` - MaxChildrenReached int `json:"max children reached"` - SlowRequests int `json:"slow requests"` -} +import ( + s "github.com/elastic/beats/metricbeat/schema" + c "github.com/elastic/beats/metricbeat/schema/mapstriface" +) + +var ( + schema = s.Schema{ + "name": c.Str("pool"), + "connections": s.Object{ + "accepted": c.Int("accepted conn"), + "queued": c.Int("listen queue"), + }, + "processes": s.Object{ + "idle": c.Int("idle processes"), + "active": c.Int("active processes"), + }, + "slow_requests": c.Int("slow requests"), + } +) diff --git a/metricbeat/module/php_fpm/pool/pool.go b/metricbeat/module/php_fpm/pool/pool.go index 982fe1e3899..bb1ede66fe6 100644 --- a/metricbeat/module/php_fpm/pool/pool.go +++ b/metricbeat/module/php_fpm/pool/pool.go @@ -6,69 +6,58 @@ import ( "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" - - "github.com/elastic/beats/metricbeat/module/php_fpm" + "github.com/elastic/beats/metricbeat/mb/parse" ) // init registers the MetricSet with the central registry. -// The New method will be called after the setup of the module and before starting to fetch data func init() { - if err := mb.Registry.AddMetricSet("php_fpm", "pool", New, php_fpm.HostParser); err != nil { + if err := mb.Registry.AddMetricSet("php_fpm", "pool", New, HostParser); err != nil { panic(err) } } +const ( + defaultScheme = "http" + defaultPath = "/status" +) + +// HostParser is used for parsing the configured php-fpm hosts. +var HostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + QueryParams: "json", + PathConfigKey: "status_path", +}.Build() + // MetricSet type defines all fields of the MetricSet -// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with -// additional entries. These variables can be used to persist data or configuration between -// multiple fetch calls. type MetricSet struct { mb.BaseMetricSet - client *php_fpm.StatsClient // StatsClient that is reused across requests. + *helper.HTTP } // New create a new instance of the MetricSet -// Part of new is also setting up the configuration by processing additional -// configuration entries if needed. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { logp.Warn("EXPERIMENTAL: The php-fpm pool metricset is experimental") return &MetricSet{ - BaseMetricSet: base, - client: php_fpm.NewStatsClient(base), + base, + helper.NewHTTP(base), }, nil } -// Fetch methods implements the data gathering and data conversion to the right format -// It returns the event which is then forward to the output. In case of an error, a -// descriptive error must be returned. +// Fetch gathers data for the pool metricset func (m *MetricSet) Fetch() (common.MapStr, error) { - body, err := m.client.Fetch() - + content, err := m.HTTP.FetchContent() if err != nil { return nil, err } - defer body.Close() - - stats := &poolStats{} - err = json.NewDecoder(body).Decode(stats) + var stats map[string]interface{} + err = json.Unmarshal(content, &stats) if err != nil { return nil, fmt.Errorf("error parsing json: %v", err) } - return common.MapStr{ - "hostname": m.Host(), - - "pool": stats.Pool, - "connections": common.MapStr{ - "queue": stats.ListenQueue, - "accepted": stats.AcceptedConn, - }, - "processes": common.MapStr{ - "idle": stats.IdleProcesses, - "active": stats.ActiveProcesses, - }, - "slow_requests": stats.SlowRequests, - }, nil + return schema.Apply(stats), nil } diff --git a/metricbeat/module/php_fpm/pool/pool_integration_test.go b/metricbeat/module/php_fpm/pool/pool_integration_test.go index b5026cfa7d2..f914d316d4c 100644 --- a/metricbeat/module/php_fpm/pool/pool_integration_test.go +++ b/metricbeat/module/php_fpm/pool/pool_integration_test.go @@ -3,6 +3,7 @@ package pool import ( + "os" "testing" mbtest "github.com/elastic/beats/metricbeat/mb/testing" @@ -20,6 +21,24 @@ func getConfig() map[string]interface{} { return map[string]interface{}{ "module": "php_fpm", "metricsets": []string{"pool"}, - "hosts": []string{"127.0.0.1:81"}, + "hosts": []string{GetEnvHost() + ":" + GetEnvPort()}, } } + +func GetEnvHost() string { + host := os.Getenv("PHPFPM_HOST") + + if len(host) == 0 { + host = "127.0.0.1" + } + return host +} + +func GetEnvPort() string { + port := os.Getenv("PHPFPM_PORT") + + if len(port) == 0 { + port = "81" + } + return port +} diff --git a/metricbeat/tests/system/test_phpfpm.py b/metricbeat/tests/system/test_phpfpm.py new file mode 100644 index 00000000000..80a81c30ca8 --- /dev/null +++ b/metricbeat/tests/system/test_phpfpm.py @@ -0,0 +1,38 @@ +import os +import metricbeat +import unittest +from nose.plugins.attrib import attr + +PHPFPM_FIELDS = metricbeat.COMMON_FIELDS + ["php_fpm"] + +class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_info(self): + """ + php_fpm pool metricset test + """ + self.render_config_template(modules=[{ + "name": "php_fpm", + "metricsets": ["pool"], + "hosts": self.get_hosts(), + "period": "5s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0) + proc.check_kill_and_wait() + + # Ensure no errors or warnings exist in the log. + log = self.get_log() + self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + + output = self.read_output_json() + self.assertEqual(len(output), 1) + evt = output[0] + + self.assertItemsEqual(self.de_dot(PHPFPM_FIELDS), evt.keys(), evt) + + self.assert_fields_are_documented(evt) + + def get_hosts(self): + return [os.getenv('PHPFPM_HOST', 'localhost') + ':' + + os.getenv('PHPFPM_PORT', '81')] From 37ed9da206f64a020422a8f6b7c9199ddb289711 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 25 Jan 2017 15:21:55 +0100 Subject: [PATCH 20/78] Docker health metricset cleanup (#3463) * Update data.json with latest data * Clean up variable naming * Add system test to also check docs * Introduce healthcheck in dockerfile to allow automated testing * Update CHANGELOG * Rename wrongly named file --- CHANGELOG.asciidoc | 1 + metricbeat/Dockerfile | 3 + .../module/docker/healthcheck/_meta/data.json | 22 +++-- metricbeat/module/docker/healthcheck/data.go | 86 +++++++++++-------- ...est.go => healthcheck_integration_test.go} | 0 metricbeat/tests/system/test_docker.py | 26 ++++++ 6 files changed, 92 insertions(+), 46 deletions(-) rename metricbeat/module/docker/healthcheck/{container_integration_test.go => healthcheck_integration_test.go} (100%) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1bfa94b249a..7972fd954be 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -86,6 +86,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add system socket module that reports all TCP sockets. {pull}3246[3246] - Kafka consumer groups metricset. {pull}3240[3240] - Add dynamic configuration reloading for modules. {pull}3281[3281] +- Add docker health metricset {pull}3357[3357] *Packetbeat* diff --git a/metricbeat/Dockerfile b/metricbeat/Dockerfile index ffcb7bb4dea..9de791219e2 100644 --- a/metricbeat/Dockerfile +++ b/metricbeat/Dockerfile @@ -12,3 +12,6 @@ ENV METRICBEAT_PATH /go/src/github.com/elastic/beats/metricbeat RUN mkdir -p $METRICBEAT_PATH/build/coverage WORKDIR $METRICBEAT_PATH + +# Add healthcheck for docker/healthcheck metricset to check during testing +HEALTHCHECK CMD exit 0 diff --git a/metricbeat/module/docker/healthcheck/_meta/data.json b/metricbeat/module/docker/healthcheck/_meta/data.json index 2b33980faa2..75564edd2b9 100644 --- a/metricbeat/module/docker/healthcheck/_meta/data.json +++ b/metricbeat/module/docker/healthcheck/_meta/data.json @@ -5,22 +5,26 @@ "name": "host.example.com" }, "docker": { + "container": { + "id": "6d01caff41f2f9118c0ced423579d70b6161eec614270e3cbf1b72e2bb40f84e", + "name": "gifted_hugle" + }, "healthcheck": { - "failingstreak": 0, - "status": "healthy", "event": { - "end_date": "2017-01-09T20:38:13.080472813+01:00", - "exit_code": 0, - "output": "this is an event output", - "start_date": "2017-01-09T20:38:12.999970865+01:00", - } + "end_date": "2017-01-25T10:58:51.171Z", + "exit_code": 0, + "output": "", + "start_date": "2017-01-25T10:58:51.114Z" + }, + "failingstreak": 0, + "status": "healthy" } }, "metricset": { "host": "/var/run/docker.sock", "module": "docker", - "name": "container", + "name": "healthcheck", "rtt": 115 }, "type": "metricsets" -} +} \ No newline at end of file diff --git a/metricbeat/module/docker/healthcheck/data.go b/metricbeat/module/docker/healthcheck/data.go index fedce1739c9..e93755bb862 100644 --- a/metricbeat/module/docker/healthcheck/data.go +++ b/metricbeat/module/docker/healthcheck/data.go @@ -1,52 +1,64 @@ package healthcheck import ( + "strings" + + dc "github.com/fsouza/go-dockerclient" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/module/docker" - - dc "github.com/fsouza/go-dockerclient" - "strings" ) -func eventsMapping(containersList []dc.APIContainers, m *MetricSet) []common.MapStr { - myEvents := []common.MapStr{} - for _, container := range containersList { - returnevent := eventMapping(&container, m) - // Compare event to empty event - if returnevent != nil { - myEvents = append(myEvents, returnevent) +func eventsMapping(containers []dc.APIContainers, m *MetricSet) []common.MapStr { + var events []common.MapStr + for _, container := range containers { + event := eventMapping(&container, m) + if event != nil { + events = append(events, event) } } - return myEvents + return events } func eventMapping(cont *dc.APIContainers, m *MetricSet) common.MapStr { - event := common.MapStr{} - // Detect if healthcheck is available for container - if strings.Contains(cont.Status, "(") && strings.Contains(cont.Status, ")") { - container, _ := m.dockerClient.InspectContainer(cont.ID) - last_event := len(container.State.Health.Log) - 1 - // Detect if an healthcheck already occured - if last_event >= 0 { - event = common.MapStr{ - mb.ModuleData: common.MapStr{ - "container": common.MapStr{ - "name": docker.ExtractContainerName(cont.Names), - "id": cont.ID, - }, - }, - "status": container.State.Health.Status, - "failingstreak": container.State.Health.FailingStreak, - "event": common.MapStr{ - "start_date": common.Time(container.State.Health.Log[last_event].Start), - "end_date": common.Time(container.State.Health.Log[last_event].End), - "exit_code": container.State.Health.Log[last_event].ExitCode, - "output": container.State.Health.Log[last_event].Output, - }, - } - return event - } + if !hasHealthCheck(cont.Status) { + return nil } - return nil + + container, err := m.dockerClient.InspectContainer(cont.ID) + if err != nil { + logp.Err("Error inpsecting container %v: %v", cont.ID, err) + return nil + } + lastEvent := len(container.State.Health.Log) - 1 + + // Checks if a healthcheck already happened + if lastEvent < 0 { + return nil + } + + return common.MapStr{ + mb.ModuleData: common.MapStr{ + "container": common.MapStr{ + "name": docker.ExtractContainerName(cont.Names), + "id": cont.ID, + }, + }, + "status": container.State.Health.Status, + "failingstreak": container.State.Health.FailingStreak, + "event": common.MapStr{ + "start_date": common.Time(container.State.Health.Log[lastEvent].Start), + "end_date": common.Time(container.State.Health.Log[lastEvent].End), + "exit_code": container.State.Health.Log[lastEvent].ExitCode, + "output": container.State.Health.Log[lastEvent].Output, + }, + } + +} + +// hasHealthCheck detects if healthcheck is available for container +func hasHealthCheck(status string) bool { + return strings.Contains(status, "(") && strings.Contains(status, ")") } diff --git a/metricbeat/module/docker/healthcheck/container_integration_test.go b/metricbeat/module/docker/healthcheck/healthcheck_integration_test.go similarity index 100% rename from metricbeat/module/docker/healthcheck/container_integration_test.go rename to metricbeat/module/docker/healthcheck/healthcheck_integration_test.go diff --git a/metricbeat/tests/system/test_docker.py b/metricbeat/tests/system/test_docker.py index 9be39ad51e6..5c41f247fa7 100644 --- a/metricbeat/tests/system/test_docker.py +++ b/metricbeat/tests/system/test_docker.py @@ -165,6 +165,32 @@ def test_network_fields(self): evt = self.remove_labels(evt) self.assert_fields_are_documented(evt) + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_health_fields(self): + """ + test health fields + """ + self.render_config_template(modules=[{ + "name": "docker", + "metricsets": ["healthcheck"], + "hosts": ["unix:///var/run/docker.sock"], + "period": "1s", + }]) + + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + # Ensure no errors or warnings exist in the log. + log = self.get_log() + self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + + output = self.read_output_json() + evt = output[0] + + evt = self.remove_labels(evt) + self.assert_fields_are_documented(evt) + def remove_labels(self, evt): if 'labels' in evt["docker"]["container"]: From badb2148bc9bdb35527bc060d48c757d30d39116 Mon Sep 17 00:00:00 2001 From: Jon Langdon Date: Wed, 25 Jan 2017 06:32:13 -0800 Subject: [PATCH 21/78] Fix processor_test.go build failure on 32-bit systems (#3446) --- libbeat/processors/processor_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libbeat/processors/processor_test.go b/libbeat/processors/processor_test.go index 06c611f82ff..3a2b520b015 100644 --- a/libbeat/processors/processor_test.go +++ b/libbeat/processors/processor_test.go @@ -117,7 +117,7 @@ func TestIncludeFields(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, }, "type": "process", @@ -135,7 +135,7 @@ func TestIncludeFields(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, }, "type": "process", @@ -185,7 +185,7 @@ func TestIncludeFields1(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, }, "type": "process", @@ -239,7 +239,7 @@ func TestDropFields(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, "type": "process", } @@ -309,7 +309,7 @@ func TestMultipleIncludeFields(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, "type": "process", } @@ -401,7 +401,7 @@ func TestDropEvent(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, }, "type": "process", @@ -446,7 +446,7 @@ func TestEmptyCondition(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, }, "type": "process", @@ -604,7 +604,7 @@ func TestDropMissingFields(t *testing.T) { "rss": 11194368, "rss_p": 0, "share": 0, - "size": 2555572224, + "size": int64(2555572224), }, "type": "process", } From e2fb93f5d5aff369c8a25b44e6e5063d5c620798 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Wed, 25 Jan 2017 16:19:22 +0100 Subject: [PATCH 22/78] Introduce libbeat.monitoring to unify collection from internal metrics (#3427) Expose some simple metrics collection via `libbeat/monitoring` package. Changes/Features: - 30s metrics snapshot is now based on `libbeat/monitoring`, adding: - support for bool/float/string variables - keys are sorted before being printed - The package manages a hierarchical registry of known KPIs (names are split on `.`) with optional support for registering created KPIs to expvar package (metrics registered via expvar can not be removed). - All metrics registered must support the `monitoring.Var` interface providing a `Visit` method for reporting metric values. The `monitoring.Visitor` explicitly limits the type of values being reportable, so no `interface{}` will be used, simplifying/unifying reporting/collecting metrics. - Registry provides `Do` method to iterate all variables, with names being 'flattened' and thanks to limitations imposed by `monitoring.Var/Visitor` the values reported can only be int64, float64, string, bool or untyped nil. - Having a registry lowers chances of typos. e.g. ``` var ( metrics := monitoring.Default.NewRegistry("libbeat.outputs.logstash") bytesSend := metrics.NewInt("bytes_send") ... ) ``` - package adds support for dynamic removal of metrics - provides adapter for go-metrics (e.g. collect stats from kafka output) with selective whitelisting/renaming... for collecting stats we otherwise would have no access to. --- libbeat/logp/logp.go | 84 +---- libbeat/logp/metrics.go | 187 ++++++++++++ .../logp/{logp_test.go => metrics_test.go} | 35 +-- libbeat/monitoring/adapter/filters.go | 144 +++++++++ libbeat/monitoring/adapter/filters_test.go | 77 +++++ .../monitoring/adapter/go-metrics-wrapper.go | 77 +++++ libbeat/monitoring/adapter/go-metrics.go | 207 +++++++++++++ libbeat/monitoring/adapter/go-metrics_test.go | 89 ++++++ libbeat/monitoring/metrics.go | 83 +++++ libbeat/monitoring/monitoring.go | 33 ++ libbeat/monitoring/opts.go | 46 +++ libbeat/monitoring/opts_test.go | 67 ++++ libbeat/monitoring/registry.go | 289 ++++++++++++++++++ libbeat/monitoring/registry_test.go | 128 ++++++++ libbeat/monitoring/visitor.go | 26 ++ libbeat/monitoring/visitor_expvar.go | 87 ++++++ libbeat/monitoring/visitor_expvar_test.go | 79 +++++ libbeat/monitoring/visitor_kv.go | 62 ++++ libbeat/outputs/kafka/kafka.go | 11 + 19 files changed, 1706 insertions(+), 105 deletions(-) create mode 100644 libbeat/logp/metrics.go rename libbeat/logp/{logp_test.go => metrics_test.go} (53%) create mode 100644 libbeat/monitoring/adapter/filters.go create mode 100644 libbeat/monitoring/adapter/filters_test.go create mode 100644 libbeat/monitoring/adapter/go-metrics-wrapper.go create mode 100644 libbeat/monitoring/adapter/go-metrics.go create mode 100644 libbeat/monitoring/adapter/go-metrics_test.go create mode 100644 libbeat/monitoring/metrics.go create mode 100644 libbeat/monitoring/monitoring.go create mode 100644 libbeat/monitoring/opts.go create mode 100644 libbeat/monitoring/opts_test.go create mode 100644 libbeat/monitoring/registry.go create mode 100644 libbeat/monitoring/registry_test.go create mode 100644 libbeat/monitoring/visitor.go create mode 100644 libbeat/monitoring/visitor_expvar.go create mode 100644 libbeat/monitoring/visitor_expvar_test.go create mode 100644 libbeat/monitoring/visitor_kv.go diff --git a/libbeat/logp/logp.go b/libbeat/logp/logp.go index 747c194309f..16684633aea 100644 --- a/libbeat/logp/logp.go +++ b/libbeat/logp/logp.go @@ -1,13 +1,11 @@ package logp import ( - "expvar" "flag" "fmt" "io/ioutil" "log" "os" - "strconv" "strings" "time" @@ -160,7 +158,7 @@ func Init(name string, config *Logging) error { log.SetOutput(ioutil.Discard) } - go logExpvars(&config.Metrics) + go logMetrics(&config.Metrics) return nil } @@ -191,83 +189,3 @@ func getLogLevel(config *Logging) (Priority, error) { } return level, nil } - -// snapshotMap recursively walks expvar Maps and records their integer expvars -// in a separate flat map. -func snapshotMap(varsMap map[string]int64, path string, mp *expvar.Map) { - mp.Do(func(kv expvar.KeyValue) { - switch kv.Value.(type) { - case *expvar.Int: - varsMap[path+"."+kv.Key], _ = strconv.ParseInt(kv.Value.String(), 10, 64) - case *expvar.Map: - snapshotMap(varsMap, path+"."+kv.Key, kv.Value.(*expvar.Map)) - } - }) -} - -// snapshotExpvars iterates through all the defined expvars, and for the vars -// that are integers it snapshots the name and value in a separate (flat) map. -func snapshotExpvars(varsMap map[string]int64) { - expvar.Do(func(kv expvar.KeyValue) { - switch kv.Value.(type) { - case *expvar.Int: - varsMap[kv.Key], _ = strconv.ParseInt(kv.Value.String(), 10, 64) - case *expvar.Map: - snapshotMap(varsMap, kv.Key, kv.Value.(*expvar.Map)) - } - }) -} - -// buildMetricsOutput makes the delta between vals and prevVals and builds -// a printable string with the non-zero deltas. -func buildMetricsOutput(prevVals map[string]int64, vals map[string]int64) string { - metrics := "" - for k, v := range vals { - delta := v - prevVals[k] - if delta != 0 { - metrics = fmt.Sprintf("%s %s=%d", metrics, k, delta) - } - } - return metrics -} - -// logExpvars logs at Info level the integer expvars that have changed in the -// last interval. For each expvar, the delta from the beginning of the interval -// is logged. -func logExpvars(metricsCfg *LoggingMetricsConfig) { - if metricsCfg.Enabled != nil && *metricsCfg.Enabled == false { - Info("Metrics logging disabled") - return - } - if metricsCfg.Period == nil { - metricsCfg.Period = &defaultMetricsPeriod - } - Info("Metrics logging every %s", metricsCfg.Period) - - ticker := time.NewTicker(*metricsCfg.Period) - prevVals := map[string]int64{} - for { - <-ticker.C - vals := map[string]int64{} - snapshotExpvars(vals) - metrics := buildMetricsOutput(prevVals, vals) - prevVals = vals - if len(metrics) > 0 { - Info("Non-zero metrics in the last %s:%s", metricsCfg.Period, metrics) - } else { - Info("No non-zero metrics in the last %s", metricsCfg.Period) - } - } -} - -func LogTotalExpvars(cfg *Logging) { - if cfg.Metrics.Enabled != nil && *cfg.Metrics.Enabled == false { - return - } - vals := map[string]int64{} - prevVals := map[string]int64{} - snapshotExpvars(vals) - metrics := buildMetricsOutput(prevVals, vals) - Info("Total non-zero values: %s", metrics) - Info("Uptime: %s", time.Now().Sub(startTime)) -} diff --git a/libbeat/logp/metrics.go b/libbeat/logp/metrics.go new file mode 100644 index 00000000000..d4b5f7b0f57 --- /dev/null +++ b/libbeat/logp/metrics.go @@ -0,0 +1,187 @@ +package logp + +import ( + "bytes" + "fmt" + "sort" + "strings" + "time" + + "github.com/elastic/beats/libbeat/monitoring" +) + +type snapshotVisitor struct { + snapshot snapshot + level []string +} + +type snapshot struct { + bools map[string]bool + ints map[string]int64 + floats map[string]float64 + strings map[string]string +} + +// logMetrics logs at Info level the integer expvars that have changed in the +// last interval. For each expvar, the delta from the beginning of the interval +// is logged. +func logMetrics(metricsCfg *LoggingMetricsConfig) { + if metricsCfg.Enabled != nil && *metricsCfg.Enabled == false { + Info("Metrics logging disabled") + return + } + if metricsCfg.Period == nil { + metricsCfg.Period = &defaultMetricsPeriod + } + Info("Metrics logging every %s", metricsCfg.Period) + + ticker := time.NewTicker(*metricsCfg.Period) + + prevVals := makeSnapshot() + for range ticker.C { + snapshot := snapshotMetrics() + delta := snapshotDelta(prevVals, snapshot) + prevVals = snapshot + + if len(delta) == 0 { + Info("No non-zero metrics in the last %s", metricsCfg.Period) + continue + } + + metrics := formatMetrics(delta) + Info("Non-zero metrics in the last %s:%s", metricsCfg.Period, metrics) + } +} + +// LogTotalExpvars logs all registered expvar metrics. +func LogTotalExpvars(cfg *Logging) { + if cfg.Metrics.Enabled != nil && *cfg.Metrics.Enabled == false { + return + } + + metrics := formatMetrics(snapshotDelta(makeSnapshot(), snapshotMetrics())) + Info("Total non-zero values: %s", metrics) + Info("Uptime: %s", time.Now().Sub(startTime)) +} + +func snapshotMetrics() snapshot { + vs := newSnapshotVisitor() + monitoring.Default.Visit(vs) + monitoring.VisitExpvars(vs) + return vs.snapshot +} + +func newSnapshotVisitor() *snapshotVisitor { + return &snapshotVisitor{snapshot: makeSnapshot()} +} + +func makeSnapshot() snapshot { + return snapshot{ + bools: map[string]bool{}, + ints: map[string]int64{}, + floats: map[string]float64{}, + strings: map[string]string{}, + } +} + +func (vs *snapshotVisitor) OnRegistryStart() error { + return nil +} + +func (vs *snapshotVisitor) OnRegistryFinished() error { + if len(vs.level) > 0 { + vs.dropName() + } + return nil +} + +func (vs *snapshotVisitor) OnKey(name string) error { + vs.level = append(vs.level, name) + return nil +} + +func (vs *snapshotVisitor) OnKeyNext() error { return nil } + +func (vs *snapshotVisitor) getName() string { + defer vs.dropName() + if len(vs.level) == 1 { + return vs.level[0] + } + return strings.Join(vs.level, ".") +} + +func (vs *snapshotVisitor) dropName() { + vs.level = vs.level[:len(vs.level)-1] +} + +func (vs *snapshotVisitor) OnString(s string) error { + vs.snapshot.strings[vs.getName()] = s + return nil +} + +func (vs *snapshotVisitor) OnBool(b bool) error { + vs.snapshot.bools[vs.getName()] = b + return nil +} + +func (vs *snapshotVisitor) OnNil() error { + vs.snapshot.strings[vs.getName()] = "" + return nil +} + +func (vs *snapshotVisitor) OnInt(i int64) error { + vs.snapshot.ints[vs.getName()] = i + return nil +} + +func (vs *snapshotVisitor) OnFloat(f float64) error { + vs.snapshot.floats[vs.getName()] = f + return nil +} + +func snapshotDelta(prev, cur snapshot) map[string]interface{} { + out := map[string]interface{}{} + + for k, b := range cur.bools { + if p, ok := prev.bools[k]; !ok || p != b { + out[k] = b + } + } + + for k, s := range cur.strings { + if p, ok := prev.strings[k]; !ok || p != s { + out[k] = s + } + } + + for k, i := range cur.ints { + if p := prev.ints[k]; p != i { + out[k] = i - p + } + } + + for k, f := range cur.floats { + if p := prev.floats[k]; p != f { + out[k] = f - p + } + } + + return out +} + +func formatMetrics(ms map[string]interface{}) string { + keys := make([]string, 0, len(ms)) + for key := range ms { + keys = append(keys, key) + } + + sort.Strings(keys) + var buf bytes.Buffer + for _, key := range keys { + buf.WriteByte(' ') + buf.WriteString(key) + buf.WriteString("=") + buf.WriteString(fmt.Sprintf("%v", ms[key])) + } + return buf.String() +} diff --git a/libbeat/logp/logp_test.go b/libbeat/logp/metrics_test.go similarity index 53% rename from libbeat/logp/logp_test.go rename to libbeat/logp/metrics_test.go index c5f57b2835a..d18dfdab586 100644 --- a/libbeat/logp/logp_test.go +++ b/libbeat/logp/metrics_test.go @@ -13,10 +13,8 @@ func TestSnapshotExpvars(t *testing.T) { test := expvar.NewInt("test") test.Add(42) - vals := map[string]int64{} - snapshotExpvars(vals) - - assert.Equal(t, vals["test"], int64(42)) + vals := snapshotMetrics() + assert.Equal(t, vals.ints["test"], int64(42)) } func TestSnapshotExpvarsMap(t *testing.T) { @@ -27,46 +25,39 @@ func TestSnapshotExpvarsMap(t *testing.T) { map2.Add("test", 5) test.Set("map2", map2) - vals := map[string]int64{} - snapshotExpvars(vals) + vals := snapshotMetrics() - assert.Equal(t, vals["testMap.hello"], int64(42)) - assert.Equal(t, vals["testMap.map2.test"], int64(5)) + assert.Equal(t, vals.ints["testMap.hello"], int64(42)) + assert.Equal(t, vals.ints["testMap.map2.test"], int64(5)) } func TestBuildMetricsOutput(t *testing.T) { test := expvar.NewInt("testLog") test.Add(1) - prevVals := map[string]int64{} - snapshotExpvars(prevVals) + prevVals := snapshotMetrics() test.Add(5) - vals := map[string]int64{} - snapshotExpvars(vals) - - metrics := buildMetricsOutput(prevVals, vals) + vals := snapshotMetrics() + metrics := formatMetrics(snapshotDelta(prevVals, vals)) assert.Equal(t, " testLog=5", metrics) prevVals = vals test.Add(3) - vals = map[string]int64{} - snapshotExpvars(vals) - metrics = buildMetricsOutput(prevVals, vals) + vals = snapshotMetrics() + metrics = formatMetrics(snapshotDelta(prevVals, vals)) assert.Equal(t, " testLog=3", metrics) } func TestBuildMetricsOutputMissing(t *testing.T) { - prevVals := map[string]int64{} - snapshotExpvars(prevVals) + prevVals := snapshotMetrics() test := expvar.NewInt("testLogEmpty") test.Add(7) - vals := map[string]int64{} - snapshotExpvars(vals) - metrics := buildMetricsOutput(prevVals, vals) + vals := snapshotMetrics() + metrics := formatMetrics(snapshotDelta(prevVals, vals)) assert.Equal(t, " testLogEmpty=7", metrics) } diff --git a/libbeat/monitoring/adapter/filters.go b/libbeat/monitoring/adapter/filters.go new file mode 100644 index 00000000000..b64d54ebc58 --- /dev/null +++ b/libbeat/monitoring/adapter/filters.go @@ -0,0 +1,144 @@ +package adapter + +import ( + "strings" + + "github.com/elastic/beats/libbeat/monitoring" +) + +// provide filters for filtering and adapting a metric type +// to monitoring.Var. + +// MetricFilter type used to defined and combine filters. +type MetricFilter func(*metricFilters) *metricFilters + +// metricFilters provides set of filters to apply to a new metric. +type metricFilters struct { + filters []varFilter +} + +type varFilter func(state) state + +// state provides the filter state to be changed by every filter. +// +// After filtering the state will be used to choose on the metric name, the +// metric type , or wether the metric is to be ignored. +type state struct { + kind kind + action action + reg *monitoring.Registry + name string + metric interface{} +} + +// action defines the action to be +type action uint8 + +// kind defines the kind of operation to be executed +type kind uint8 + +const ( + kndFind kind = iota + kndAdd + kndRemove +) + +const ( + actIgnore action = iota + actAccept +) + +func makeFilters(in ...MetricFilter) *metricFilters { + if len(in) == 0 { + return nil + } + + m := &metricFilters{} + for _, mk := range in { + m = mk(m) + } + return m +} + +func (m *metricFilters) apply(st state) state { + if m != nil { + for _, filter := range m.filters { + st = filter(st) + } + } + return st +} + +func ApplyIf(pred func(name string) bool, filters ...MetricFilter) MetricFilter { + then := makeFilters(filters...) + return withVarFilter(func(st state) state { + if pred(st.name) { + st = then.apply(st) + } + return st + }) +} + +var Accept = withVarFilter(func(st state) state { + st.action = actAccept + return st +}) + +// WhitelistIf will accept a metric if the metrics name matches +// the given predicate. +func WhitelistIf(pred func(string) bool) MetricFilter { + return ApplyIf(pred, Accept) +} + +// Whitelist sets a list of metric names to be accepted. +func Whitelist(names ...string) MetricFilter { + return WhitelistIf(func(name string) bool { + for _, n := range names { + if name == n { + return true + } + } + return false + }) +} + +// ModifyName changes a metric its name using the provided +// function. +func ModifyName(f func(string) string) MetricFilter { + return withVarFilter(func(st state) state { + st.name = f(st.name) + return st + }) +} + +// Rename renames a metric to `to`, if the names matches `from` +// If the name matches, it will be automatically white-listed. +func Rename(from, to string) MetricFilter { + return withVarFilter(func(st state) state { + if st.name == from { + st.action, st.name = actAccept, to + } + return st + }) +} + +// NameReplace replaces substrings in a metrics names with `new`. +func NameReplace(old, new string) MetricFilter { + return ModifyName(func(name string) string { + return strings.Replace(name, old, new, -1) + }) +} + +// ToLowerName converts all metric names to lower-case +var ToLowerName = ModifyName(strings.ToLower) + +// ToUpperName converts all metric name to upper-case +var ToUpperName = ModifyName(strings.ToUpper) + +// withVarFilter lifts a varFilter into a MetricFilter +func withVarFilter(f varFilter) MetricFilter { + return func(m *metricFilters) *metricFilters { + m.filters = append(m.filters, f) + return m + } +} diff --git a/libbeat/monitoring/adapter/filters_test.go b/libbeat/monitoring/adapter/filters_test.go new file mode 100644 index 00000000000..477a9e826c7 --- /dev/null +++ b/libbeat/monitoring/adapter/filters_test.go @@ -0,0 +1,77 @@ +package adapter + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilters(t *testing.T) { + tests := []struct { + start state + filters *metricFilters + expected state + }{ + { + state{action: actIgnore, name: "test"}, + nil, + state{action: actIgnore, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters(), + state{action: actIgnore, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters( + WhitelistIf(func(_ string) bool { return true }), + ), + state{action: actAccept, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters( + WhitelistIf(func(_ string) bool { return false }), + ), + state{action: actIgnore, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters(Whitelist("other")), + state{action: actIgnore, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters(Whitelist("test")), + state{action: actAccept, name: "test"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters(Rename("test", "new")), + state{action: actAccept, name: "new"}, + }, + { + state{action: actIgnore, name: "t-e-s-t"}, + makeFilters(NameReplace("-", ".")), + state{action: actIgnore, name: "t.e.s.t"}, + }, + { + state{action: actIgnore, name: "test"}, + makeFilters(ToUpperName), + state{action: actIgnore, name: "TEST"}, + }, + { + state{action: actIgnore, name: "TEST"}, + makeFilters(ToLowerName), + state{action: actIgnore, name: "test"}, + }, + } + + for i, test := range tests { + t.Logf("run test (%v): %v => %v", i, test.start, test.expected) + + actual := test.filters.apply(test.start) + assert.Equal(t, test.expected, actual) + } +} diff --git a/libbeat/monitoring/adapter/go-metrics-wrapper.go b/libbeat/monitoring/adapter/go-metrics-wrapper.go new file mode 100644 index 00000000000..f3c4f722d59 --- /dev/null +++ b/libbeat/monitoring/adapter/go-metrics-wrapper.go @@ -0,0 +1,77 @@ +package adapter + +import ( + "github.com/elastic/beats/libbeat/monitoring" + metrics "github.com/rcrowley/go-metrics" +) + +// go-metrics wrapper interface required to unpack the original metric +type goMetricsWrapper interface { + wrapped() interface{} +} + +// go-metrics wrappers +type ( + goMetricsCounter struct{ c metrics.Counter } + + goMetricsGauge struct{ g metrics.Gauge } + goMetricsGaugeFloat64 struct{ g metrics.GaugeFloat64 } + + goMetricsFuncGauge struct{ g metrics.FunctionalGauge } + goMetricsFuncGaugeFloat struct { + g metrics.FunctionalGaugeFloat64 + } + + goMetricsHistogram struct{ h metrics.Histogram } + + goMetricsMeter struct{ m metrics.Meter } +) + +// goMetricsWrap tries to wrap a metric for use with monitoring package. +func goMetricsWrap(metric interface{}) (monitoring.Var, bool) { + switch v := metric.(type) { + case *metrics.StandardCounter: + return goMetricsCounter{v}, true + case *metrics.StandardGauge: + return goMetricsGauge{v}, true + case *metrics.StandardGaugeFloat64: + return goMetricsGaugeFloat64{v}, true + case metrics.FunctionalGauge: + return goMetricsFuncGauge{v}, true + case metrics.FunctionalGaugeFloat64: + return goMetricsFuncGaugeFloat{v}, true + case *metrics.StandardHistogram: + return goMetricsHistogram{v}, true + case *metrics.StandardMeter: + return goMetricsMeter{v}, true + } + return nil, false +} + +func (w goMetricsCounter) wrapped() interface{} { return w.c } +func (w goMetricsCounter) Get() int64 { return w.c.Count() } +func (w goMetricsCounter) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } + +func (w goMetricsGauge) wrapped() interface{} { return w.g } +func (w goMetricsGauge) Get() int64 { return w.g.Value() } +func (w goMetricsGauge) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } + +func (w goMetricsGaugeFloat64) wrapped() interface{} { return w.g } +func (w goMetricsGaugeFloat64) Get() float64 { return w.g.Value() } +func (w goMetricsGaugeFloat64) Visit(vs monitoring.Visitor) error { return vs.OnFloat(w.Get()) } + +func (w goMetricsFuncGauge) wrapped() interface{} { return w.g } +func (w goMetricsFuncGauge) Get() int64 { return w.g.Value() } +func (w goMetricsFuncGauge) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } + +func (w goMetricsFuncGaugeFloat) wrapped() interface{} { return w.g } +func (w goMetricsFuncGaugeFloat) Get() float64 { return w.g.Value() } +func (w goMetricsFuncGaugeFloat) Visit(vs monitoring.Visitor) error { return vs.OnFloat(w.Get()) } + +func (w goMetricsHistogram) wrapped() interface{} { return w.h } +func (w goMetricsHistogram) Get() int64 { return w.h.Sum() } +func (w goMetricsHistogram) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } + +func (w goMetricsMeter) wrapped() interface{} { return w.m } +func (w goMetricsMeter) Get() int64 { return w.m.Count() } +func (w goMetricsMeter) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } diff --git a/libbeat/monitoring/adapter/go-metrics.go b/libbeat/monitoring/adapter/go-metrics.go new file mode 100644 index 00000000000..bd3f7e1030b --- /dev/null +++ b/libbeat/monitoring/adapter/go-metrics.go @@ -0,0 +1,207 @@ +package adapter + +import ( + "fmt" + "reflect" + + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/monitoring" + metrics "github.com/rcrowley/go-metrics" +) + +// implement adapter for adding go-metrics based counters +// to monitoring + +// GoMetricsRegistry wraps a monitoring.Registry for filtering and registering +// go-metrics based metrics with the monitoring package. GoMetricsRegistry implements +// the go-metrics.Registry interface. +// +// Note: with the go-metrics using `interface{}`, there is no guarantee +// a variable satisfying any of go-metrics interfaces is returned. +// It's recommended to not mix go-metrics with other metrics types +// in the same namespace. +type GoMetricsRegistry struct { + reg *monitoring.Registry + filters *metricFilters + + shadow metrics.Registry // store non-accepted metrics +} + +// GetGoMetrics wraps an existing monitoring.Registry with `name` into a +// GoMetricsRegistry for using the registry with go-metrics.Registry. +// If the monitoring.Registry does not exist yet, a new one will be generated. +// +// Note: with users of go-metrics potentially removing any metric at runtime, +// it's recommended to have the underlying registry being generated with +// `monitoring.IgnorePublishExpvar`. +func GetGoMetrics(parent *monitoring.Registry, name string, filters ...MetricFilter) *GoMetricsRegistry { + v := parent.Get(name) + if v == nil { + return NewGoMetrics(parent, name, filters...) + } + + reg := v.(*monitoring.Registry) + return &GoMetricsRegistry{ + reg: reg, + shadow: metrics.NewRegistry(), + filters: makeFilters(filters...), + } +} + +// NewGoMetrics creates and registers a new GoMetricsRegistry with the parent +// registry. +func NewGoMetrics(parent *monitoring.Registry, name string, filters ...MetricFilter) *GoMetricsRegistry { + return &GoMetricsRegistry{ + reg: parent.NewRegistry(name, monitoring.IgnorePublishExpvar), + shadow: metrics.NewRegistry(), + filters: makeFilters(filters...), + } +} + +// Each only iterates the shadowed metrics, not registered to the monitoring package, +// as those metrics are owned by monitoring.Registry only. +func (r *GoMetricsRegistry) Each(f func(string, interface{})) { + r.shadow.Each(f) +} + +func (r *GoMetricsRegistry) find(name string) interface{} { + st := r.findState(name) + if st.action == actIgnore { + return nil + } + + return r.reg.Get(st.name) +} + +// Get retrieves a registered metric by name. If the name is unknown, Get returns nil. +// +// Note: with the return values being `interface{}`, there is no guarantee +// a variable satisfying any of go-metrics interfaces is returned. +// It's recommended to not mix go-metrics with other metrics types in one +// namespace. +func (r *GoMetricsRegistry) Get(name string) interface{} { + m := r.find(name) + if m == nil { + return r.shadow.Get(name) + } + + if w, ok := m.(goMetricsWrapper); ok { + return w.wrapped() + } + + return m +} + +// GetOrRegister retries an existing metric via `Get` or registers a new one +// if the metric is unknown. For lazy instantiation metric can be a function. +func (r *GoMetricsRegistry) GetOrRegister(name string, metric interface{}) interface{} { + v := r.Get(name) + if v != nil { + return v + } + + return r.doRegister(name, metric) +} + +// Register adds a new metric. +// An error is returned if the metric is already known. +func (r *GoMetricsRegistry) Register(name string, metric interface{}) error { + if r.Get(name) != nil { + return fmt.Errorf("metric '%v' already registered", name) + } + + r.doRegister(name, metric) + return nil +} + +func (r *GoMetricsRegistry) doRegister(name string, metric interface{}) interface{} { + if v := reflect.ValueOf(metric); v.Kind() == reflect.Func { + metric = v.Call(nil)[0].Interface() + } + + st := r.addState(name, metric) + if st.action == actIgnore { + return r.shadow.GetOrRegister(name, st.metric) + } + + if st.action == actAccept { + w, ok := goMetricsWrap(st.metric) + if ok { + r.reg.Add(st.name, w) + } + } + + return st.metric +} + +// RunHealthchecks is a noop, required to satisfy the metrics.Registry interface. +func (r *GoMetricsRegistry) RunHealthchecks() {} + +// Unregister removes a metric. +func (r *GoMetricsRegistry) Unregister(name string) { + st := r.rmState(name) + r.reg.Remove(st.name) + r.shadow.Unregister(name) +} + +// UnregisterAll calls `Clear` on the underlying monitoring.Registry +func (r *GoMetricsRegistry) UnregisterAll() { + r.shadow.UnregisterAll() + err := r.reg.Clear() + if err != nil { + logp.Err("Failed to clear registry: %v", err) + } +} + +func (r *GoMetricsRegistry) findState(name string) state { + return r.stateWith(kndFind, name, nil) +} + +func (r *GoMetricsRegistry) addState(name string, metric interface{}) state { + return r.stateWith(kndAdd, name, metric) +} + +func (r *GoMetricsRegistry) rmState(name string) state { + return r.stateWith(kndRemove, name, nil) +} + +func (r *GoMetricsRegistry) stateWith(k kind, name string, metric interface{}) state { + return r.filters.apply(state{ + kind: k, + action: actIgnore, + reg: r.reg, + name: name, + metric: metric, + }) +} + +// GoMetricsRegistry MetricFilter used to convert all metrics not being +// accepted by the filters to be replace with a Noop-metric. +// This can be used to disable metrics in go-metrics users lazily generating +// metrics via GetOrRegister. +var GoMetricsNilify = withVarFilter(func(st state) state { + if st.action != actIgnore { + return st + } + + switch st.metric.(type) { + case *metrics.StandardCounter: + st.metric = metrics.NilCounter{} + case *metrics.StandardEWMA: + st.metric = metrics.NilEWMA{} + case *metrics.StandardGauge: + st.metric = metrics.NilGauge{} + case *metrics.StandardGaugeFloat64: + st.metric = metrics.NilGaugeFloat64{} + case *metrics.StandardHealthcheck: + st.metric = metrics.NilHealthcheck{} + case *metrics.StandardHistogram: + st.metric = metrics.NilHistogram{} + case *metrics.StandardMeter: + st.metric = metrics.NilMeter{} + case *metrics.StandardTimer: + st.metric = metrics.NilTimer{} + } + + return st +}) diff --git a/libbeat/monitoring/adapter/go-metrics_test.go b/libbeat/monitoring/adapter/go-metrics_test.go new file mode 100644 index 00000000000..b46086c84b1 --- /dev/null +++ b/libbeat/monitoring/adapter/go-metrics_test.go @@ -0,0 +1,89 @@ +package adapter + +import ( + "strings" + "testing" + + "github.com/elastic/beats/libbeat/monitoring" + metrics "github.com/rcrowley/go-metrics" + "github.com/stretchr/testify/assert" +) + +func TestGoMetricsAdapter(t *testing.T) { + filters := []MetricFilter{ + WhitelistIf(func(name string) bool { + return strings.HasPrefix(name, "mon") + }), + ApplyIf( + func(name string) bool { + return strings.HasPrefix(name, "ign") + }, + GoMetricsNilify, + ), + } + + counters := map[string]int64{ + "mon-counter": 42, + "ign-counter": 0, + "counter": 42, + } + meters := map[string]int64{ + "mon-meter": 23, + "ign-meter": 0, + "meter": 23, + } + + monReg := monitoring.NewRegistry() + var reg metrics.Registry = GetGoMetrics(monReg, "test", filters...) + + // register some metrics and check they're satisfying the go-metrics interface + // no matter if owned by monitoring or go-metrics + for name := range counters { + cnt := reg.GetOrRegister(name, func() interface{} { + return metrics.NewCounter() + }).(metrics.Counter) + cnt.Clear() + } + + for name := range meters { + meter := reg.GetOrRegister(name, func() interface{} { + return metrics.NewMeter() + }).(metrics.Meter) + meter.Count() + } + + // get and increase registered metrics + for name := range counters { + cnt := reg.Get(name).(metrics.Counter) + cnt.Inc(21) + cnt.Inc(21) + } + for name := range meters { + meter := reg.Get(name).(metrics.Meter) + meter.Mark(11) + meter.Mark(12) + } + + // compare metric values to expected values + for name, value := range counters { + cnt := reg.Get(name).(metrics.Counter) + assert.Equal(t, value, cnt.Count()) + } + for name, value := range meters { + meter := reg.Get(name).(metrics.Meter) + assert.Equal(t, value, meter.Count()) + } + + // check Each only returns metrics not registered with monitoring.Registry + reg.Each(func(name string, v interface{}) { + if strings.HasPrefix(name, "mon") { + t.Errorf("metric %v should not have been reported by each", name) + } + }) + monReg.Do(func(name string, v interface{}) error { + if !strings.HasPrefix(name, "test.mon") { + t.Errorf("metric %v should not have been reported by each", name) + } + return nil + }) +} diff --git a/libbeat/monitoring/metrics.go b/libbeat/monitoring/metrics.go new file mode 100644 index 00000000000..58e65a4754e --- /dev/null +++ b/libbeat/monitoring/metrics.go @@ -0,0 +1,83 @@ +package monitoring + +import ( + "math" + "sync" + "sync/atomic" +) + +// makeExpvar wraps a callback for registering a metrics with expvar.Publish. +type makeExpvar func() string + +// Int is a 64 bit integer variable satisfying the Var interface. +type Int struct{ i int64 } + +// NewInt registers a new global integer metrics. +func NewInt(name string) *Int { + return Default.NewInt(name) +} + +func (v *Int) Visit(vs Visitor) error { return vs.OnInt(v.Get()) } +func (v *Int) Get() int64 { return atomic.LoadInt64(&v.i) } +func (v *Int) Set(value int64) { atomic.StoreInt64(&v.i, value) } +func (v *Int) Add(delta int64) { atomic.AddInt64(&v.i, delta) } +func (v *Int) Inc() { atomic.AddInt64(&v.i, 1) } +func (v *Int) Dec() { atomic.AddInt64(&v.i, -1) } + +// Float is a 64 bit float variable satisfying the Var interface. +type Float struct{ f uint64 } + +// NewFloat registers a new global floating point metric. +func NewFloat(name string) *Float { + return Default.NewFloat(name) +} + +func (v *Float) Visit(vs Visitor) error { return vs.OnFloat(v.Get()) } +func (v *Float) Get() float64 { return math.Float64frombits(atomic.LoadUint64(&v.f)) } +func (v *Float) Set(value float64) { atomic.StoreUint64(&v.f, math.Float64bits(value)) } +func (v *Float) Sub(delta float64) { v.Add(-delta) } + +func (v *Float) Add(delta float64) { + for { + cur := atomic.LoadUint64(&v.f) + next := math.Float64bits(math.Float64frombits(cur) + delta) + if atomic.CompareAndSwapUint64(&v.f, cur, next) { + return + } + } +} + +// String is a string variable satisfying the Var interface. +type String struct { + mu sync.RWMutex + s string +} + +// NewString registers a new global string metric. +func NewString(name string) *String { + return Default.NewString(name) +} + +func (v *String) Visit(vs Visitor) error { return vs.OnString(v.Get()) } + +func (v *String) Get() string { + v.mu.RLock() + defer v.mu.RUnlock() + return v.s +} + +func (v *String) Set(s string) { + v.mu.Lock() + defer v.mu.Unlock() + v.s = s +} + +func (v *String) Clear() { + v.Set("") +} + +func (v *String) Fail(err error) { + v.Set(err.Error()) +} + +func (m makeExpvar) String() string { return m() } diff --git a/libbeat/monitoring/monitoring.go b/libbeat/monitoring/monitoring.go new file mode 100644 index 00000000000..8735526dd82 --- /dev/null +++ b/libbeat/monitoring/monitoring.go @@ -0,0 +1,33 @@ +package monitoring + +import "errors" + +// Default is the global default metrics registry provided by the monitoring package. +var Default = NewRegistry() + +var errNotFound = errors.New("Name unknown") +var errInvalidName = errors.New("Name does not point to a valid variable") + +func Visit(vs Visitor) error { + return Default.Visit(vs) +} + +func Do(f func(string, interface{}) error) error { + return Default.Do(f) +} + +func Get(name string) interface{} { + return Default.Get(name) +} + +func GetRegistry(name string) *Registry { + return Default.GetRegistry(name) +} + +func Remove(name string) { + Default.Remove(name) +} + +func Clear() error { + return Default.Clear() +} diff --git a/libbeat/monitoring/opts.go b/libbeat/monitoring/opts.go new file mode 100644 index 00000000000..bae2b02cd58 --- /dev/null +++ b/libbeat/monitoring/opts.go @@ -0,0 +1,46 @@ +package monitoring + +// Option type for passing additional options to NewRegistry. +type Option func(options) options + +type options struct { + publishExpvar bool +} + +var defaultOptions = options{ + publishExpvar: false, +} + +// PublishExpvar enables publishing all registered variables via expvar interface. +// Note: expvar does not allow removal of any stats. +func PublishExpvar(o options) options { + o.publishExpvar = true + return o +} + +// IgnorePublishExpvar disables publishing expvar variables in a sub-registry. +func IgnorePublishExpvar(o options) options { + o.publishExpvar = false + return o +} + +func applyOpts(in *options, opts []Option) *options { + if len(opts) == 0 { + return ensureOptions(in) + } + + tmp := *ensureOptions(in) + for _, opt := range opts { + tmp = opt(tmp) + } + return &tmp +} + +func ensureOptions(in *options) *options { + if in != nil { + return in + } + + tmp := defaultOptions + return &tmp +} diff --git a/libbeat/monitoring/opts_test.go b/libbeat/monitoring/opts_test.go new file mode 100644 index 00000000000..fb72f1fc426 --- /dev/null +++ b/libbeat/monitoring/opts_test.go @@ -0,0 +1,67 @@ +// +build !integration + +package monitoring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOptions(t *testing.T) { + tests := []struct { + name string + parent *options + options []Option + expected options + }{ + { + "empty parent without opts should generate defaults", + nil, + nil, + defaultOptions, + }, + { + "non empty parent should return same options", + &options{}, + nil, + options{}, + }, + { + "apply publishexpvar", + &options{publishExpvar: false}, + []Option{PublishExpvar}, + options{publishExpvar: true}, + }, + { + "apply disable publishexpvar", + &options{publishExpvar: true}, + []Option{IgnorePublishExpvar}, + options{publishExpvar: false}, + }, + } + + for i, test := range tests { + t.Logf("run test (%v): %v", i, test.name) + + origParent := options{} + if test.parent != nil { + origParent = *test.parent + } + actual := applyOpts(test.parent, test.options) + assert.NotNil(t, actual) + + // test parent has not been modified by accident + if test.parent != nil { + assert.Equal(t, origParent, *test.parent) + } + + // check parent and actual are same object if options is nil + if test.parent != nil && test.options == nil { + assert.Equal(t, test.parent, actual) + } + + // validate output + assert.Equal(t, test.expected, *actual) + } +} diff --git a/libbeat/monitoring/registry.go b/libbeat/monitoring/registry.go new file mode 100644 index 00000000000..cb45ccf8481 --- /dev/null +++ b/libbeat/monitoring/registry.go @@ -0,0 +1,289 @@ +package monitoring + +import ( + "encoding/json" + "errors" + "expvar" + "fmt" + "strconv" + "strings" + "sync" +) + +// Registry to store variables and sub-registries. +// When adding or retrieving variables, all names are split on the `.`-symbol and +// intermediate registries will be generated. +type Registry struct { + mu sync.RWMutex + + name string + entries map[string]Var + + opts *options +} + +// Var interface required for every metric to implement. +type Var interface { + Visit(Visitor) error +} + +// NewRegistry create a new empty unregistered registry +func NewRegistry(opts ...Option) *Registry { + return &Registry{ + opts: applyOpts(nil, opts), + entries: map[string]Var{}, + } +} + +func (r *Registry) Do(f func(string, interface{}) error) error { + return r.Visit(NewKeyValueVisitor(f)) +} + +// Visit uses the Visitor interface to iterate the complete metrics hieararchie. +// In case of the visitor reporting an error, Visit will return immediately, +// reporting the very same error. +func (r *Registry) Visit(vs Visitor) error { + if err := vs.OnRegistryStart(); err != nil { + return err + } + + r.mu.RLock() + defer r.mu.RUnlock() + + first := true + for key, v := range r.entries { + if first { + first = false + } else { + if err := vs.OnKeyNext(); err != nil { + return err + } + } + + if err := vs.OnKey(key); err != nil { + return err + } + + if err := v.Visit(vs); err != nil { + return err + } + } + + return vs.OnRegistryFinished() +} + +// NewRegistry creates and register a new registry +func (r *Registry) NewRegistry(name string, opts ...Option) *Registry { + v := &Registry{ + name: r.fullName(name), + opts: applyOpts(r.opts, opts), + entries: map[string]Var{}, + } + r.Add(name, v) + return v +} + +// NewInt creates and registers a new integer variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func (r *Registry) NewInt(name string) *Int { + v := &Int{} + r.Add(name, v) + r.publish(name, makeExpvar(func() string { + return strconv.FormatInt(v.Get(), 10) + })) + return v +} + +// NewFloat creates and registers a new float variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func (r *Registry) NewFloat(name string) *Float { + v := &Float{} + r.Add(name, v) + r.publish(name, makeExpvar(func() string { + return strconv.FormatFloat(v.Get(), 'g', -1, 64) + })) + return v +} + +// NewString creates and registers a new string variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func (r *Registry) NewString(name string) *String { + v := &String{} + r.Add(name, v) + r.publish(name, makeExpvar(func() string { + b, _ := json.Marshal(v.Get()) + return string(b) + })) + return v +} + +// Get tries to find a registered variable by name. +func (r *Registry) Get(name string) interface{} { + v, err := r.find(name) + if err != nil { + return nil + } + return v +} + +// GetRegistry tries to find a sub-registry by name. +func (r *Registry) GetRegistry(name string) *Registry { + v, err := r.find(name) + if err != nil { + return nil + } + + if v == nil { + return nil + } + + reg, ok := v.(*Registry) + if !ok { + return nil + } + + return reg +} + +// Remove removes a variable or a sub-registry by name +func (r *Registry) Remove(name string) { + r.removeNames(strings.Split(name, ".")) +} + +// Clear removes all entries from the current registry +func (r *Registry) Clear() error { + r.mu.Lock() + r.mu.Unlock() + + if r.opts.publishExpvar { + return errors.New("Can not clear registry with metrics being exported via expvar") + } + + r.entries = map[string]Var{} + return nil +} + +func (r *Registry) publish(name string, v expvar.Var) { + if !r.opts.publishExpvar { + return + } + + expvar.Publish(r.fullName(name), v) +} + +func (r *Registry) fullName(name string) string { + if r.name == "" { + return name + } + return r.name + "." + name +} + +// Add adds a new variable to the registry. The method panics if the variables +// name is already in use. +func (r *Registry) Add(name string, v Var) { + panicErr(r.addNames(strings.Split(name, "."), v)) +} + +func (r *Registry) addNames(names []string, v Var) error { + r.mu.Lock() + defer r.mu.Unlock() + + name := names[0] + if len(names) == 1 { + if _, found := r.entries[name]; found { + return fmt.Errorf("name %v already used", name) + } + + r.entries[name] = v + return nil + } + + if tmp, found := r.entries[name]; found { + reg, ok := tmp.(*Registry) + if !ok { + return fmt.Errorf("name %v already used", name) + } + + return reg.addNames(names[1:], v) + } + + sub := NewRegistry() + sub.opts = r.opts + if err := sub.addNames(names[1:], v); err != nil { + return err + } + + r.entries[name] = sub + return nil +} + +func (r *Registry) find(name string) (interface{}, error) { + return r.findNames(strings.Split(name, ".")) +} + +func (r *Registry) findNames(names []string) (interface{}, error) { + switch len(names) { + case 0: + return r, nil + case 1: + r.mu.RLock() + defer r.mu.RUnlock() + return r.entries[names[0]], nil + } + + r.mu.RLock() + next := r.entries[names[0]] + r.mu.RUnlock() + + if next == nil { + return nil, errNotFound + } + + if reg, ok := next.(*Registry); ok { + return reg.findNames(names[1:]) + } + return nil, errInvalidName +} + +func (r *Registry) removeNames(names []string) { + switch len(names) { + case 0: + return + case 1: + r.mu.Lock() + defer r.mu.Unlock() + delete(r.entries, names[0]) + return + } + + r.mu.Lock() + defer r.mu.Unlock() + next := r.entries[names[0]] + sub, ok := next.(*Registry) + + // if name does not exist => don't remove anything + if ok { + sub.removeNames(names[1:]) + sub.mu.RLock() + sub.mu.RUnlock() + + if len(sub.entries) == 0 { + delete(r.entries, names[0]) + } + } +} + +func panicErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/libbeat/monitoring/registry_test.go b/libbeat/monitoring/registry_test.go new file mode 100644 index 00000000000..d8222996956 --- /dev/null +++ b/libbeat/monitoring/registry_test.go @@ -0,0 +1,128 @@ +// +build !integration + +package monitoring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRegistryEmpty(t *testing.T) { + defer Clear() + + // get value + v := Get("missing") + if v != nil { + t.Errorf("got %v, wanted nil", v) + } + + // get value with recursive lookup + v = Get("missing.value") + if v != nil { + t.Errorf("got %v, wanted nil", v) + } + + // get missing registry + reg := GetRegistry("missing") + if reg != nil { + t.Errorf("got %v, wanted nil", reg) + } + + // get registry with recursive lookup + reg = GetRegistry("missing.registry") + if reg != nil { + t.Errorf("got %v, wanted nil", reg) + } +} + +func TestRegistryGet(t *testing.T) { + defer Clear() + + name1 := "v" + nameSub1 := "sub.registry1" + nameSub2 := "sub.registry2" + name2 := nameSub1 + "." + name1 + name3 := nameSub2 + "." + name1 + + // register top-level and recursive metric + v1 := NewInt(name1) + sub1 := Default.NewRegistry(nameSub1) + sub2 := Default.NewRegistry(nameSub2) + v2 := NewString(name2) + v3 := sub2.NewFloat(name1) + + // get values + v := Get(name1) + assert.Equal(t, v, v1) + + // get nested metric from top-level + v = Get(name2) + assert.Equal(t, v, v2) + v = Get(name3) + assert.Equal(t, v, v3) + + // get sub registry + reg1 := GetRegistry(nameSub1) + assert.Equal(t, sub1, reg1) + reg2 := GetRegistry(nameSub2) + assert.Equal(t, sub2, reg2) + + // get value from sub-registry + v = reg1.Get(name1) + assert.Equal(t, v, v2) + + v = reg2.Get(name1) + assert.Equal(t, v, v3) +} + +func TestRegistryRemove(t *testing.T) { + defer Clear() + + name1 := "v" + nameSub1 := "sub.registry1" + nameSub2 := "sub.registry2" + name2 := nameSub1 + "." + name1 + name3 := nameSub2 + "." + name1 + + // register top-level and recursive metric + NewInt(name1) + sub1 := Default.NewRegistry(nameSub1) + sub2 := Default.NewRegistry(nameSub2) + NewInt(name2) + sub2.NewInt(name1) + + // remove metrics: + Remove(name1) + sub1.Remove(name1) // == Remove(name2) + Remove(name3) // remove name 3 recursively + + // check no variable is reachable + assert.Nil(t, Get(name1)) + assert.Nil(t, Get(name2)) + assert.Nil(t, Get(name3)) +} + +func TestRegistryIter(t *testing.T) { + defer Clear() + + vars := map[string]int64{ + "sub.registry.v1": 1, + "sub.registry.v2": 2, + "v3": 3, + } + + for name, v := range vars { + i := NewInt(name) + i.Add(v) + } + + collected := map[string]int64{} + err := Do(func(name string, v interface{}) error { + collected[name] = v.(int64) + return nil + }) + + assert.Nil(t, err) + assert.Equal(t, vars, collected) +} diff --git a/libbeat/monitoring/visitor.go b/libbeat/monitoring/visitor.go new file mode 100644 index 00000000000..78fb9669ac9 --- /dev/null +++ b/libbeat/monitoring/visitor.go @@ -0,0 +1,26 @@ +package monitoring + +// Visitor interface supports traversing a monitoring registry +type Visitor interface { + ValueVisitor + RegistryVisitor +} + +type ValueVisitor interface { + OnString(s string) error + OnBool(b bool) error + OnNil() error + + // int + OnInt(i int64) error + + // float + OnFloat(f float64) error +} + +type RegistryVisitor interface { + OnRegistryStart() error + OnRegistryFinished() error + OnKey(s string) error + OnKeyNext() error +} diff --git a/libbeat/monitoring/visitor_expvar.go b/libbeat/monitoring/visitor_expvar.go new file mode 100644 index 00000000000..51372c232ae --- /dev/null +++ b/libbeat/monitoring/visitor_expvar.go @@ -0,0 +1,87 @@ +package monitoring + +import ( + "encoding/json" + "expvar" + "strconv" +) + +// VisitExpvars iterates all expvar metrics using the Visitor interface. +// The top-level metrics "memstats" and "cmdline", plus all monitoring.X metric types +// are ignored. +func VisitExpvars(vs Visitor) { + vs.OnRegistryStart() + expvar.Do(makeExparVisitor(0, vs)) + vs.OnRegistryFinished() +} + +func DoExpvars(f func(string, interface{})) { + VisitExpvars(NewKeyValueVisitor(func(name string, v interface{}) error { + f(name, v) + return nil + })) +} + +func makeExparVisitor(level int, vs Visitor) func(expvar.KeyValue) { + first := true + return func(kv expvar.KeyValue) { + if ignoreExpvar(level, kv) { + return + } + + if first { + first = false + } else { + vs.OnKeyNext() + } + + name := kv.Key + variable := kv.Value + switch v := variable.(type) { + case *expvar.Int: + i, _ := strconv.ParseInt(v.String(), 10, 64) + vs.OnKey(name) + vs.OnInt(i) + + case *expvar.Float: + f, _ := strconv.ParseFloat(v.String(), 64) + vs.OnKey(name) + vs.OnFloat(f) + + case *expvar.Map: + vs.OnKey(name) + vs.OnRegistryStart() + v.Do(makeExparVisitor(level+1, vs)) + vs.OnRegistryFinished() + + default: + vs.OnKey(name) + s := v.String() + if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { + var tmp string + if err := json.Unmarshal([]byte(s), &tmp); err == nil { + s = tmp + } + } + vs.OnString(s) + } + } +} + +// ignore if `monitoring` variable or some other internals +// autmoatically registered by expvar against our wishes +func ignoreExpvar(level int, kv expvar.KeyValue) bool { + switch kv.Value.(type) { + case makeExpvar, Var: + return true + } + + if level == 0 { + switch kv.Key { + case "memstats", "cmdline": + return true + } + } + + return false +} diff --git a/libbeat/monitoring/visitor_expvar_test.go b/libbeat/monitoring/visitor_expvar_test.go new file mode 100644 index 00000000000..b59ecd38c25 --- /dev/null +++ b/libbeat/monitoring/visitor_expvar_test.go @@ -0,0 +1,79 @@ +// +build !integration + +package monitoring + +import ( + "expvar" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIterExpvarIgnoringMonitoringVars(t *testing.T) { + vars := map[string]int64{ + "sub.registry.v1": 1, + "sub.registry.v2": 2, + "v3": 3, + } + collected := map[string]int64{} + + reg := NewRegistry(PublishExpvar) + for name, v := range vars { + i := reg.NewInt(name) + i.Add(v) + } + + DoExpvars(func(name string, v interface{}) { + if _, exists := vars[name]; exists { + collected[name] = v.(int64) + } + }) + assert.Equal(t, map[string]int64{}, collected) +} + +func TestIterExpvarCaptureVars(t *testing.T) { + i := getOrCreateInt("test.integer") + i.Set(42) + + s := getOrCreateString("test.string") + s.Set("testing") + + var m *expvar.Map + if v := expvar.Get("test.map"); v != nil { + m = v.(*expvar.Map) + } else { + m = expvar.NewMap("test.map") + m.Add("i1", 1) + m.Add("i2", 2) + } + + expected := map[string]interface{}{ + "test.integer": int64(42), + "test.string": "testing", + "test.map.i1": int64(1), + "test.map.i2": int64(2), + } + + collected := map[string]interface{}{} + DoExpvars(func(name string, v interface{}) { + if _, exists := expected[name]; exists { + collected[name] = v + } + }) + + assert.Equal(t, collected, expected) +} + +func getOrCreateInt(name string) *expvar.Int { + if v := expvar.Get(name); v != nil { + return v.(*expvar.Int) + } + return expvar.NewInt(name) +} + +func getOrCreateString(name string) *expvar.String { + if v := expvar.Get(name); v != nil { + return v.(*expvar.String) + } + return expvar.NewString(name) +} diff --git a/libbeat/monitoring/visitor_kv.go b/libbeat/monitoring/visitor_kv.go new file mode 100644 index 00000000000..13daf631579 --- /dev/null +++ b/libbeat/monitoring/visitor_kv.go @@ -0,0 +1,62 @@ +package monitoring + +import "strings" + +type KeyValueVisitor struct { + cb func(key string, value interface{}) error + level []string +} + +func NewKeyValueVisitor(cb func(string, interface{}) error) *KeyValueVisitor { + return &KeyValueVisitor{cb: cb} +} + +func (vs *KeyValueVisitor) OnRegistryStart() error { + return nil +} + +func (vs *KeyValueVisitor) OnRegistryFinished() error { + if len(vs.level) > 0 { + vs.dropName() + } + return nil +} + +func (vs *KeyValueVisitor) OnKey(name string) error { + vs.level = append(vs.level, name) + return nil +} + +func (vs *KeyValueVisitor) OnKeyNext() error { return nil } + +func (vs *KeyValueVisitor) getName() string { + defer vs.dropName() + if len(vs.level) == 1 { + return vs.level[0] + } + return strings.Join(vs.level, ".") +} + +func (vs *KeyValueVisitor) dropName() { + vs.level = vs.level[:len(vs.level)-1] +} + +func (vs *KeyValueVisitor) OnString(s string) error { + return vs.cb(vs.getName(), s) +} + +func (vs *KeyValueVisitor) OnBool(b bool) error { + return vs.cb(vs.getName(), b) +} + +func (vs *KeyValueVisitor) OnNil() error { + return vs.cb(vs.getName(), nil) +} + +func (vs *KeyValueVisitor) OnInt(i int64) error { + return vs.cb(vs.getName(), i) +} + +func (vs *KeyValueVisitor) OnFloat(f float64) error { + return vs.cb(vs.getName(), f) +} diff --git a/libbeat/outputs/kafka/kafka.go b/libbeat/outputs/kafka/kafka.go index ebe9df654c3..f90e0c8d6f2 100644 --- a/libbeat/outputs/kafka/kafka.go +++ b/libbeat/outputs/kafka/kafka.go @@ -14,6 +14,8 @@ import ( "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/common/op" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/monitoring" + "github.com/elastic/beats/libbeat/monitoring/adapter" "github.com/elastic/beats/libbeat/outputs" "github.com/elastic/beats/libbeat/outputs/mode" "github.com/elastic/beats/libbeat/outputs/mode/modeutil" @@ -270,6 +272,15 @@ func (k *kafka) newKafkaConfig() (*sarama.Config, error) { } cfg.Producer.Partitioner = k.partitioner + + // TODO: figure out which metrics we want to collect + cfg.MetricRegistry = adapter.GetGoMetrics( + monitoring.Default, + "libbeat.output.kafka", + adapter.Rename("incoming-byte-rate", "bytes_read"), + adapter.Rename("outgoing-byte-rate", "bytes_write"), + adapter.GoMetricsNilify, + ) return cfg, nil } From 3265970394a6ffcd53466d1a4e89a971d691519d Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 25 Jan 2017 14:19:15 -0800 Subject: [PATCH 23/78] Add beta header to every page (#3458) --- heartbeat/docs/overview.asciidoc | 2 -- heartbeat/docs/page_header.html | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 heartbeat/docs/page_header.html diff --git a/heartbeat/docs/overview.asciidoc b/heartbeat/docs/overview.asciidoc index 2450ce58ffb..a259c46be86 100644 --- a/heartbeat/docs/overview.asciidoc +++ b/heartbeat/docs/overview.asciidoc @@ -1,8 +1,6 @@ [[heartbeat-overview]] == Overview -beta[] - Heartbeat is a lightweight daemon that you install on a remote server to periodically check the status of your services and determine whether they are available. Unlike {metricbeat}/index.html[Metricbeat], which only tells you if diff --git a/heartbeat/docs/page_header.html b/heartbeat/docs/page_header.html new file mode 100644 index 00000000000..6bb0e637fa8 --- /dev/null +++ b/heartbeat/docs/page_header.html @@ -0,0 +1,4 @@ +This functionality is in beta and is subject to change. The design and +code is considered to be less mature than official GA features. Elastic will +take a best effort approach to fix any issues, but beta features are not +subject to the support SLA of official GA features. From bb8301ba58a58e0b8f79aefd384d7752bfda1db6 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Thu, 26 Jan 2017 12:07:51 +0100 Subject: [PATCH 24/78] Remove simplify target in Makefile as not needed anymore (#3464) `make fmt` now also includes the `-s` flag. --- libbeat/scripts/Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index 4dd1b302850..fdadc5d447a 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -91,13 +91,9 @@ check: ## @build Checks project and source code if everything is according to s go vet ${GOPACKAGES} .PHONY: fmt -fmt: ## @build Runs gofmt -w on the project's source code, modifying any files that do not match its style. +fmt: ## @build Runs `gofmt -s -l -w` on the project's source code, modifying any files that do not match its style. gofmt -s -l -w ${GOFILES_NOVENDOR} -.PHONY: simplify -simplify: ## @build Runs gofmt -s -w on the project's source code, modifying any files that do not match its style. - gofmt -l -s -w ${GOFILES_NOVENDOR} - .PHONY: clean clean:: ## @build Cleans up all files generated by the build steps rm -rf build ${BEAT_NAME} ${BEAT_NAME}.test ${BEAT_NAME}.exe ${BEAT_NAME}.test.exe _meta/fields.generated.yml From cd9becd7af44b828d58992c2c7bfd79f41c79d5e Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Thu, 26 Jan 2017 12:49:33 +0100 Subject: [PATCH 25/78] Return empty registry if module path doesn't exist (#3407) This is a follow up for #3405, to avoid having a nil moduleRegistry. Also adds a unit test for that case. --- filebeat/beater/filebeat.go | 10 +++------- filebeat/fileset/modules.go | 4 ++-- filebeat/fileset/modules_test.go | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index 3f3bb7c8882..1249fb712f9 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -43,13 +43,9 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) { return nil, err } - var moduleProspectors []*common.Config - if moduleRegistry != nil { - var err error - moduleProspectors, err = moduleRegistry.GetProspectorConfigs() - if err != nil { - return nil, err - } + moduleProspectors, err := moduleRegistry.GetProspectorConfigs() + if err != nil { + return nil, err } if err := config.FetchConfigs(); err != nil { diff --git a/filebeat/fileset/modules.go b/filebeat/fileset/modules.go index d1dfa705504..4dba8132b80 100644 --- a/filebeat/fileset/modules.go +++ b/filebeat/fileset/modules.go @@ -86,8 +86,8 @@ func NewModuleRegistry(moduleConfigs []*common.Config) (*ModuleRegistry, error) stat, err := os.Stat(modulesPath) if err != nil || !stat.IsDir() { - logp.Info("Not loading modules. Module directory not found: %s") - return nil, nil + logp.Err("Not loading modules. Module directory not found: %s", modulesPath) + return &ModuleRegistry{}, nil // empty registry, no error } modulesCLIList, modulesOverrides, err := getModulesCLIConfig() diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index 5b36947d67c..555014f4b00 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/paths" "github.com/stretchr/testify/assert" ) @@ -324,3 +325,22 @@ func TestMcfgFromConfig(t *testing.T) { } } } + +func TestMissingModuleFolder(t *testing.T) { + home := paths.Paths.Home + paths.Paths.Home = "/no/such/path" + defer func() { paths.Paths.Home = home }() + + configs := []*common.Config{ + load(t, map[string]interface{}{"module": "nginx"}), + } + + reg, err := NewModuleRegistry(configs) + assert.NoError(t, err) + assert.NotNil(t, reg) + + // this should return an empty list, but no error + prospectors, err := reg.GetProspectorConfigs() + assert.NoError(t, err) + assert.Equal(t, 0, len(prospectors)) +} From 667e7c54b4b717746240cda82b12263060494d43 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Thu, 26 Jan 2017 14:33:00 +0100 Subject: [PATCH 26/78] Set the pipeline ID in the prospector configuration (#3472) Now that prospectors have a pipeline config option, use it in the filebeat modules. This gets rid of the requirement to have a weird pipeline option in the output, and removes one hack from the fields. Part of #3159. --- filebeat/fileset/fileset.go | 12 ++++++++---- filebeat/fileset/fileset_test.go | 10 +++++++--- filebeat/module/apache2/access/config/access.yml | 1 - filebeat/module/apache2/error/config/error.yml | 1 - filebeat/module/mysql/error/config/error.yml | 1 - filebeat/module/mysql/slowlog/config/slowlog.yml | 1 - filebeat/module/nginx/access/config/nginx-access.yml | 1 - filebeat/module/nginx/error/config/nginx-error.yml | 1 - filebeat/module/syslog/system/config/system.yml | 1 - filebeat/tests/system/config/filebeat_modules.yml.j2 | 1 - 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/filebeat/fileset/fileset.go b/filebeat/fileset/fileset.go index 0473f269981..e723dd3ed76 100644 --- a/filebeat/fileset/fileset.go +++ b/filebeat/fileset/fileset.go @@ -27,6 +27,7 @@ type Fileset struct { modulePath string manifest *manifest vars map[string]interface{} + pipelineID string } // New allocates a new Fileset object with the given configuration. @@ -62,13 +63,10 @@ func (fs *Fileset) Read() error { return err } - pipeline_id, err := fs.getPipelineID() + fs.pipelineID, err = fs.getPipelineID() if err != nil { return err } - fs.vars["beat"] = map[string]interface{}{ - "pipeline_id": pipeline_id, - } return nil } @@ -231,6 +229,12 @@ func (fs *Fileset) getProspectorConfig() (*common.Config, error) { } } + // force our pipeline ID + err = cfg.SetString("pipeline", -1, fs.pipelineID) + if err != nil { + return nil, fmt.Errorf("Error setting the pipeline ID in the prospector config: %v", err) + } + cfg.PrintDebugf("Merged prospector config for fileset %s/%s", fs.mcfg.Module, fs.name) return cfg, nil diff --git a/filebeat/fileset/fileset_test.go b/filebeat/fileset/fileset_test.go index b65f04d03d2..6c8bfbf5701 100644 --- a/filebeat/fileset/fileset_test.go +++ b/filebeat/fileset/fileset_test.go @@ -156,8 +156,10 @@ func TestGetProspectorConfigNginx(t *testing.T) { assert.True(t, cfg.HasField("paths")) assert.True(t, cfg.HasField("exclude_files")) - pipeline_id := fs.vars["beat"].(map[string]interface{})["pipeline_id"] - assert.Equal(t, "nginx-access-with_plugins", pipeline_id) + assert.True(t, cfg.HasField("pipeline")) + pipelineID, err := cfg.String("pipeline", -1) + assert.NoError(t, err) + assert.Equal(t, "nginx-access-with_plugins", pipelineID) } func TestGetProspectorConfigNginxOverrides(t *testing.T) { @@ -178,7 +180,9 @@ func TestGetProspectorConfigNginxOverrides(t *testing.T) { assert.True(t, cfg.HasField("paths")) assert.True(t, cfg.HasField("exclude_files")) assert.True(t, cfg.HasField("close_eof")) - pipelineID := fs.vars["beat"].(map[string]interface{})["pipeline_id"] + assert.True(t, cfg.HasField("pipeline")) + pipelineID, err := cfg.String("pipeline", -1) + assert.NoError(t, err) assert.Equal(t, "nginx-access-with_plugins", pipelineID) } diff --git a/filebeat/module/apache2/access/config/access.yml b/filebeat/module/apache2/access/config/access.yml index 4352af8acb8..34d8fb14c2d 100644 --- a/filebeat/module/apache2/access/config/access.yml +++ b/filebeat/module/apache2/access/config/access.yml @@ -6,4 +6,3 @@ paths: exclude_files: [".gz$"] fields: source_type: apache2-access - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/apache2/error/config/error.yml b/filebeat/module/apache2/error/config/error.yml index 90a1bf76a71..e2dc79a9435 100644 --- a/filebeat/module/apache2/error/config/error.yml +++ b/filebeat/module/apache2/error/config/error.yml @@ -6,4 +6,3 @@ paths: exclude_files: [".gz$"] fields: source_type: apache2-error - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/mysql/error/config/error.yml b/filebeat/module/mysql/error/config/error.yml index 5b9c0ec2094..7986c05a466 100644 --- a/filebeat/module/mysql/error/config/error.yml +++ b/filebeat/module/mysql/error/config/error.yml @@ -6,4 +6,3 @@ paths: exclude_files: [".gz$"] fields: source_type: mysql-error - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/mysql/slowlog/config/slowlog.yml b/filebeat/module/mysql/slowlog/config/slowlog.yml index 695b0102f6e..8ae68bd79c8 100644 --- a/filebeat/module/mysql/slowlog/config/slowlog.yml +++ b/filebeat/module/mysql/slowlog/config/slowlog.yml @@ -10,4 +10,3 @@ multiline: match: after fields: source_type: mysql-slowlog - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/nginx/access/config/nginx-access.yml b/filebeat/module/nginx/access/config/nginx-access.yml index 14e50d35f74..6e79adcb43d 100644 --- a/filebeat/module/nginx/access/config/nginx-access.yml +++ b/filebeat/module/nginx/access/config/nginx-access.yml @@ -6,4 +6,3 @@ paths: exclude_files: [".gz$"] fields: source_type: nginx-access - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/nginx/error/config/nginx-error.yml b/filebeat/module/nginx/error/config/nginx-error.yml index 1cadbc02620..c878eeedc90 100644 --- a/filebeat/module/nginx/error/config/nginx-error.yml +++ b/filebeat/module/nginx/error/config/nginx-error.yml @@ -6,4 +6,3 @@ paths: exclude_files: [".gz$"] fields: source_type: nginx-error - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/module/syslog/system/config/system.yml b/filebeat/module/syslog/system/config/system.yml index 1641dd5e849..dfea10dc28d 100644 --- a/filebeat/module/syslog/system/config/system.yml +++ b/filebeat/module/syslog/system/config/system.yml @@ -9,4 +9,3 @@ multiline: match: after fields: source_type: syslog-system - pipeline_id: {{.beat.pipeline_id}} diff --git a/filebeat/tests/system/config/filebeat_modules.yml.j2 b/filebeat/tests/system/config/filebeat_modules.yml.j2 index 87e2937e35c..b7aa09abdcf 100644 --- a/filebeat/tests/system/config/filebeat_modules.yml.j2 +++ b/filebeat/tests/system/config/filebeat_modules.yml.j2 @@ -3,4 +3,3 @@ filebeat.registry_file: {{ beat.working_dir + '/' }}{{ registryFile|default("reg output.elasticsearch.hosts: ["{{ elasticsearch_url }}"] output.elasticsearch.index: {{ index_name }} -output.elasticsearch.pipeline: "%{[fields.pipeline_id]}" From d2168acdd071efba961c64e7e48d6b81a422e426 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Thu, 26 Jan 2017 15:09:33 +0100 Subject: [PATCH 27/78] Auto generate imports for protocols in Packetbeat (#3468) This will allow to use the protocol generator and directly create the correct imports. --- packetbeat/Makefile | 8 ++++- packetbeat/include/list.go | 25 +++++++++++++++ packetbeat/main.go | 14 +-------- packetbeat/scripts/generate_imports.py | 42 ++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 packetbeat/include/list.go create mode 100644 packetbeat/scripts/generate_imports.py diff --git a/packetbeat/Makefile b/packetbeat/Makefile index e35aa112e9d..8541c66add1 100644 --- a/packetbeat/Makefile +++ b/packetbeat/Makefile @@ -28,7 +28,7 @@ before-build: # Collects all dependencies and then calls update .PHONY: collect -collect: fields +collect: fields imports .PHONY: fields fields: @@ -42,3 +42,9 @@ benchmark: .PHONY: create-tcp-protocol create-tcp-protocol: python scripts/create_tcp_protocol.py + +# Generates imports for all modules and metricsets +.PHONY: imports +imports: + mkdir -p include + python ${ES_BEATS}/packetbeat/scripts/generate_imports.py ${BEAT_PATH} > include/list.go diff --git a/packetbeat/include/list.go b/packetbeat/include/list.go new file mode 100644 index 00000000000..f9196dd45f1 --- /dev/null +++ b/packetbeat/include/list.go @@ -0,0 +1,25 @@ +/* +Package include imports all protos packages so that they register with the global +registry. This package can be imported in the main package to automatically register +all of the standard supported Packetbeat protocols. +*/ +package include + +import ( + // This list is automatically generated by `make imports` + _ "github.com/elastic/beats/packetbeat/protos/amqp" + _ "github.com/elastic/beats/packetbeat/protos/applayer" + _ "github.com/elastic/beats/packetbeat/protos/cassandra" + _ "github.com/elastic/beats/packetbeat/protos/dns" + _ "github.com/elastic/beats/packetbeat/protos/http" + _ "github.com/elastic/beats/packetbeat/protos/icmp" + _ "github.com/elastic/beats/packetbeat/protos/memcache" + _ "github.com/elastic/beats/packetbeat/protos/mongodb" + _ "github.com/elastic/beats/packetbeat/protos/mysql" + _ "github.com/elastic/beats/packetbeat/protos/nfs" + _ "github.com/elastic/beats/packetbeat/protos/pgsql" + _ "github.com/elastic/beats/packetbeat/protos/redis" + _ "github.com/elastic/beats/packetbeat/protos/tcp" + _ "github.com/elastic/beats/packetbeat/protos/thrift" + _ "github.com/elastic/beats/packetbeat/protos/udp" +) diff --git a/packetbeat/main.go b/packetbeat/main.go index 8f7a2b22f09..9b63cba2546 100644 --- a/packetbeat/main.go +++ b/packetbeat/main.go @@ -4,20 +4,8 @@ import ( "os" "github.com/elastic/beats/libbeat/beat" + _ "github.com/elastic/beats/metricbeat/include" "github.com/elastic/beats/packetbeat/beater" - - // import support protocol modules - _ "github.com/elastic/beats/packetbeat/protos/amqp" - _ "github.com/elastic/beats/packetbeat/protos/cassandra" - _ "github.com/elastic/beats/packetbeat/protos/dns" - _ "github.com/elastic/beats/packetbeat/protos/http" - _ "github.com/elastic/beats/packetbeat/protos/memcache" - _ "github.com/elastic/beats/packetbeat/protos/mongodb" - _ "github.com/elastic/beats/packetbeat/protos/mysql" - _ "github.com/elastic/beats/packetbeat/protos/nfs" - _ "github.com/elastic/beats/packetbeat/protos/pgsql" - _ "github.com/elastic/beats/packetbeat/protos/redis" - _ "github.com/elastic/beats/packetbeat/protos/thrift" ) var Name = "packetbeat" diff --git a/packetbeat/scripts/generate_imports.py b/packetbeat/scripts/generate_imports.py new file mode 100644 index 00000000000..2c9d5d0f94e --- /dev/null +++ b/packetbeat/scripts/generate_imports.py @@ -0,0 +1,42 @@ +import os +import sys + +# Generates the file list.go with all modules and metricsets + +header = """/* +Package include imports all protos packages so that they register with the global +registry. This package can be imported in the main package to automatically register +all of the standard supported Packetbeat protocols. +*/ +package include + +import ( +\t// This list is automatically generated by `make imports` +""" + + +def generate(go_beat_path): + + base_dir = "protos" + path = os.path.abspath("protos") + list_file = header + + # Fetch all protocols + for protocol in sorted(os.listdir(base_dir)): + + if os.path.isfile(path + "/" + protocol): + continue + + list_file += ' _ "' + go_beat_path + '/protos/' + protocol + '"\n' + + list_file += ")" + + # output string so it can be concatenated + print list_file + +if __name__ == "__main__": + # First argument is the beat path under GOPATH. + # (e.g. github.com/elastic/beats/packetbeat) + go_beat_path = sys.argv[1] + + generate(go_beat_path) From 3fce4405fa0a28007753243748d67dd7b2d1af8a Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Fri, 27 Jan 2017 14:56:51 +0100 Subject: [PATCH 28/78] Replace *regexp.Regexp with match.Matcher (#3469) * Use matchers in processor conditionals * Filebeat include/exclude lines/files using match.Matcher * Update filebeat multiline to use match.Matcher * Metricbeat system module whitelist using new string matcher * Remove `match` conditional * Update changelog --- CHANGELOG.asciidoc | 3 + filebeat/harvester/config.go | 6 +- filebeat/harvester/log.go | 4 +- filebeat/harvester/log_test.go | 22 +- filebeat/harvester/reader/multiline.go | 17 +- filebeat/harvester/reader/multiline_config.go | 5 +- filebeat/harvester/reader/multiline_test.go | 12 +- filebeat/harvester/util.go | 12 +- filebeat/harvester/util_test.go | 23 +- filebeat/prospector/config.go | 24 +- filebeat/prospector/prospector_log.go | 2 +- .../prospector/prospector_log_other_test.go | 8 +- .../prospector/prospector_log_windows_test.go | 6 +- filebeat/prospector/prospector_test.go | 8 +- libbeat/common/match/matcher.go | 43 ++++ libbeat/processors/condition.go | 211 +++++++----------- metricbeat/module/system/process/helper.go | 14 +- 17 files changed, 208 insertions(+), 212 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 7972fd954be..678caecde7c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -64,6 +64,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Files created by Beats (logs, registry, file output) will have 0600 permissions. {pull}3387[3387]. - RPM/deb packages will now install the config file with 0600 permissions. {pull}3382[3382] - Add the option to pass custom HTTP headers to the Elasticsearch output. {pull}3400[3400] +- Unify `regexp` and `contains` conditionals, for both to support array of strings and convert numbers to strings if required. {pull}3469[3469] *Metricbeat* @@ -87,6 +88,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Kafka consumer groups metricset. {pull}3240[3240] - Add dynamic configuration reloading for modules. {pull}3281[3281] - Add docker health metricset {pull}3357[3357] +- System module uses new matchers for white-listing processes. {pull}3469[3469] *Packetbeat* @@ -96,6 +98,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add enabled config option to prospectors. {pull}3157[3157] - Add target option for decoded_json_field. {pull}3169[3169] - Add the `pipeline` config option at the prospector level, for configuring the Ingest Node pipeline ID. {pull}3433[3433] +- Update regular expressions used for matching file names or lines (multiline, include/exclude functionality) to new matchers improving performance of simple string matches. {pull}3469[3469] *Winlogbeat* diff --git a/filebeat/harvester/config.go b/filebeat/harvester/config.go index d1baaf9a638..c4c8068fe48 100644 --- a/filebeat/harvester/config.go +++ b/filebeat/harvester/config.go @@ -2,12 +2,12 @@ package harvester import ( "fmt" - "regexp" "time" cfg "github.com/elastic/beats/filebeat/config" "github.com/elastic/beats/filebeat/harvester/reader" "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/common/match" "github.com/dustin/go-humanize" "github.com/elastic/beats/libbeat/logp" @@ -47,8 +47,8 @@ type harvesterConfig struct { CloseEOF bool `config:"close_eof"` CloseTimeout time.Duration `config:"close_timeout" validate:"min=0"` ForceCloseFiles bool `config:"force_close_files"` - ExcludeLines []*regexp.Regexp `config:"exclude_lines"` - IncludeLines []*regexp.Regexp `config:"include_lines"` + ExcludeLines []match.Matcher `config:"exclude_lines"` + IncludeLines []match.Matcher `config:"include_lines"` MaxBytes int `config:"max_bytes" validate:"min=0,nonzero"` Multiline *reader.MultilineConfig `config:"multiline"` JSON *reader.JSONConfig `config:"json"` diff --git a/filebeat/harvester/log.go b/filebeat/harvester/log.go index a47bf67809c..7b8a4a2e775 100644 --- a/filebeat/harvester/log.go +++ b/filebeat/harvester/log.go @@ -163,14 +163,14 @@ func (h *Harvester) sendEvent(event *input.Event) bool { // the include_lines and exclude_lines options. func (h *Harvester) shouldExportLine(line string) bool { if len(h.config.IncludeLines) > 0 { - if !MatchAnyRegexps(h.config.IncludeLines, line) { + if !MatchAny(h.config.IncludeLines, line) { // drop line logp.Debug("harvester", "Drop line as it does not match any of the include patterns %s", line) return false } } if len(h.config.ExcludeLines) > 0 { - if MatchAnyRegexps(h.config.ExcludeLines, line) { + if MatchAny(h.config.ExcludeLines, line) { // drop line logp.Debug("harvester", "Drop line as it does match one of the exclude patterns%s", line) return false diff --git a/filebeat/harvester/log_test.go b/filebeat/harvester/log_test.go index 910d611ac76..8862b15c8dc 100644 --- a/filebeat/harvester/log_test.go +++ b/filebeat/harvester/log_test.go @@ -104,29 +104,23 @@ func TestReadLine(t *testing.T) { } func TestExcludeLine(t *testing.T) { - - regexp, err := InitRegexps([]string{"^DBG"}) - + regexp, err := InitMatchers("^DBG") assert.Nil(t, err) - - assert.True(t, MatchAnyRegexps(regexp, "DBG: a debug message")) - assert.False(t, MatchAnyRegexps(regexp, "ERR: an error message")) + assert.True(t, MatchAny(regexp, "DBG: a debug message")) + assert.False(t, MatchAny(regexp, "ERR: an error message")) } func TestIncludeLine(t *testing.T) { - - regexp, err := InitRegexps([]string{"^ERR", "^WARN"}) + regexp, err := InitMatchers("^ERR", "^WARN") assert.Nil(t, err) - - assert.False(t, MatchAnyRegexps(regexp, "DBG: a debug message")) - assert.True(t, MatchAnyRegexps(regexp, "ERR: an error message")) - assert.True(t, MatchAnyRegexps(regexp, "WARNING: a simple warning message")) + assert.False(t, MatchAny(regexp, "DBG: a debug message")) + assert.True(t, MatchAny(regexp, "ERR: an error message")) + assert.True(t, MatchAny(regexp, "WARNING: a simple warning message")) } func TestInitRegexp(t *testing.T) { - - _, err := InitRegexps([]string{"((((("}) + _, err := InitMatchers("(((((") assert.NotNil(t, err) } diff --git a/filebeat/harvester/reader/multiline.go b/filebeat/harvester/reader/multiline.go index 661286c4dee..edc50d4d522 100644 --- a/filebeat/harvester/reader/multiline.go +++ b/filebeat/harvester/reader/multiline.go @@ -3,8 +3,9 @@ package reader import ( "errors" "fmt" - "regexp" "time" + + "github.com/elastic/beats/libbeat/common/match" ) // MultiLine reader combining multiple line events into one multi-line event. @@ -55,7 +56,7 @@ func NewMultiline( maxBytes int, config *MultilineConfig, ) (*Multiline, error) { - types := map[string]func(*regexp.Regexp) (matcher, error){ + types := map[string]func(match.Matcher) (matcher, error){ "before": beforeMatcher, "after": afterMatcher, } @@ -280,14 +281,14 @@ func (mlr *Multiline) setState(next func(mlr *Multiline) (Message, error)) { // matchers -func afterMatcher(regex *regexp.Regexp) (matcher, error) { - return genPatternMatcher(regex, func(last, current []byte) []byte { +func afterMatcher(pat match.Matcher) (matcher, error) { + return genPatternMatcher(pat, func(last, current []byte) []byte { return current }) } -func beforeMatcher(regex *regexp.Regexp) (matcher, error) { - return genPatternMatcher(regex, func(last, current []byte) []byte { +func beforeMatcher(pat match.Matcher) (matcher, error) { + return genPatternMatcher(pat, func(last, current []byte) []byte { return last }) } @@ -299,12 +300,12 @@ func negatedMatcher(m matcher) matcher { } func genPatternMatcher( - regex *regexp.Regexp, + pat match.Matcher, sel func(last, current []byte) []byte, ) (matcher, error) { matcher := func(last, current []byte) bool { line := sel(last, current) - return regex.Match(line) + return pat.Match(line) } return matcher, nil } diff --git a/filebeat/harvester/reader/multiline_config.go b/filebeat/harvester/reader/multiline_config.go index 5e52e426938..783b24386b8 100644 --- a/filebeat/harvester/reader/multiline_config.go +++ b/filebeat/harvester/reader/multiline_config.go @@ -2,15 +2,16 @@ package reader import ( "fmt" - "regexp" "time" + + "github.com/elastic/beats/libbeat/common/match" ) type MultilineConfig struct { Negate bool `config:"negate"` Match string `config:"match" validate:"required"` MaxLines *int `config:"max_lines"` - Pattern *regexp.Regexp `config:"pattern"` + Pattern match.Matcher `config:"pattern"` Timeout *time.Duration `config:"timeout" validate:"positive"` } diff --git a/filebeat/harvester/reader/multiline_test.go b/filebeat/harvester/reader/multiline_test.go index 5042031823b..16434f30bd2 100644 --- a/filebeat/harvester/reader/multiline_test.go +++ b/filebeat/harvester/reader/multiline_test.go @@ -6,12 +6,12 @@ import ( "bytes" "errors" "os" - "regexp" "strings" "testing" "time" "github.com/elastic/beats/filebeat/harvester/encoding" + "github.com/elastic/beats/libbeat/common/match" "github.com/stretchr/testify/assert" ) @@ -26,7 +26,7 @@ func (p bufferSource) Continuable() bool { return false } func TestMultilineAfterOK(t *testing.T) { testMultilineOK(t, MultilineConfig{ - Pattern: regexp.MustCompile(`^[ \t] +`), // next line is indented by spaces + Pattern: match.MustCompile(`^[ \t] +`), // next line is indented by spaces Match: "after", }, 2, @@ -38,7 +38,7 @@ func TestMultilineAfterOK(t *testing.T) { func TestMultilineBeforeOK(t *testing.T) { testMultilineOK(t, MultilineConfig{ - Pattern: regexp.MustCompile(`\\$`), // previous line ends with \ + Pattern: match.MustCompile(`\\$`), // previous line ends with \ Match: "before", }, 2, @@ -50,7 +50,7 @@ func TestMultilineBeforeOK(t *testing.T) { func TestMultilineAfterNegateOK(t *testing.T) { testMultilineOK(t, MultilineConfig{ - Pattern: regexp.MustCompile(`^-`), // first line starts with '-' at beginning of line + Pattern: match.MustCompile(`^-`), // first line starts with '-' at beginning of line Negate: true, Match: "after", }, @@ -63,7 +63,7 @@ func TestMultilineAfterNegateOK(t *testing.T) { func TestMultilineBeforeNegateOK(t *testing.T) { testMultilineOK(t, MultilineConfig{ - Pattern: regexp.MustCompile(`;$`), // last line ends with ';' + Pattern: match.MustCompile(`;$`), // last line ends with ';' Negate: true, Match: "before", }, @@ -76,7 +76,7 @@ func TestMultilineBeforeNegateOK(t *testing.T) { func TestMultilineBeforeNegateOKWithEmptyLine(t *testing.T) { testMultilineOK(t, MultilineConfig{ - Pattern: regexp.MustCompile(`;$`), // last line ends with ';' + Pattern: match.MustCompile(`;$`), // last line ends with ';' Negate: true, Match: "before", }, diff --git a/filebeat/harvester/util.go b/filebeat/harvester/util.go index 1cc7d0ad412..b847f74ad70 100644 --- a/filebeat/harvester/util.go +++ b/filebeat/harvester/util.go @@ -1,15 +1,13 @@ package harvester -import "regexp" +import "github.com/elastic/beats/libbeat/common/match" -// MatchAnyRegexps checks if the text matches any of the regular expressions -func MatchAnyRegexps(regexps []*regexp.Regexp, text string) bool { - - for _, rexp := range regexps { - if rexp.MatchString(text) { +// MatchAny checks if the text matches any of the regular expressions +func MatchAny(matchers []match.Matcher, text string) bool { + for _, m := range matchers { + if m.MatchString(text) { return true } } - return false } diff --git a/filebeat/harvester/util_test.go b/filebeat/harvester/util_test.go index ec3ca77f36c..7900c4541a5 100644 --- a/filebeat/harvester/util_test.go +++ b/filebeat/harvester/util_test.go @@ -3,20 +3,20 @@ package harvester import ( - "regexp" "testing" - "github.com/elastic/beats/libbeat/logp" "github.com/stretchr/testify/assert" -) -// InitRegexps initializes a list of compiled regular expressions. -func InitRegexps(exprs []string) ([]*regexp.Regexp, error) { + "github.com/elastic/beats/libbeat/common/match" + "github.com/elastic/beats/libbeat/logp" +) - result := []*regexp.Regexp{} +// InitMatchers initializes a list of compiled regular expressions. +func InitMatchers(exprs ...string) ([]match.Matcher, error) { + result := []match.Matcher{} for _, exp := range exprs { - rexp, err := regexp.Compile(exp) + rexp, err := match.Compile(exp) if err != nil { logp.Err("Fail to compile the regexp %s: %s", exp, err) return nil, err @@ -27,13 +27,8 @@ func InitRegexps(exprs []string) ([]*regexp.Regexp, error) { } func TestMatchAnyRegexps(t *testing.T) { - - patterns := []string{"\\.gz$"} - - regexps, err := InitRegexps(patterns) - + matchers, err := InitMatchers("\\.gz$") assert.Nil(t, err) - - assert.Equal(t, MatchAnyRegexps(regexps, "/var/log/log.gz"), true) + assert.Equal(t, MatchAny(matchers, "/var/log/log.gz"), true) } diff --git a/filebeat/prospector/config.go b/filebeat/prospector/config.go index 1c0e8156319..7bcc88eb99a 100644 --- a/filebeat/prospector/config.go +++ b/filebeat/prospector/config.go @@ -2,10 +2,10 @@ package prospector import ( "fmt" - "regexp" "time" cfg "github.com/elastic/beats/filebeat/config" + "github.com/elastic/beats/libbeat/common/match" ) var ( @@ -23,17 +23,17 @@ var ( ) type prospectorConfig struct { - Enabled bool `config:"enabled"` - ExcludeFiles []*regexp.Regexp `config:"exclude_files"` - IgnoreOlder time.Duration `config:"ignore_older"` - Paths []string `config:"paths"` - ScanFrequency time.Duration `config:"scan_frequency" validate:"min=0,nonzero"` - InputType string `config:"input_type"` - CleanInactive time.Duration `config:"clean_inactive" validate:"min=0"` - CleanRemoved bool `config:"clean_removed"` - HarvesterLimit uint64 `config:"harvester_limit" validate:"min=0"` - Symlinks bool `config:"symlinks"` - TailFiles bool `config:"tail_files"` + Enabled bool `config:"enabled"` + ExcludeFiles []match.Matcher `config:"exclude_files"` + IgnoreOlder time.Duration `config:"ignore_older"` + Paths []string `config:"paths"` + ScanFrequency time.Duration `config:"scan_frequency" validate:"min=0,nonzero"` + InputType string `config:"input_type"` + CleanInactive time.Duration `config:"clean_inactive" validate:"min=0"` + CleanRemoved bool `config:"clean_removed"` + HarvesterLimit uint64 `config:"harvester_limit" validate:"min=0"` + Symlinks bool `config:"symlinks"` + TailFiles bool `config:"tail_files"` } func (config *prospectorConfig) Validate() error { diff --git a/filebeat/prospector/prospector_log.go b/filebeat/prospector/prospector_log.go index b4ae09fc7e5..ff4b62a5c61 100644 --- a/filebeat/prospector/prospector_log.go +++ b/filebeat/prospector/prospector_log.go @@ -342,7 +342,7 @@ func (p *ProspectorLog) handleIgnoreOlder(lastState, newState file.State) error // isFileExcluded checks if the given path should be excluded func (p *ProspectorLog) isFileExcluded(file string) bool { patterns := p.config.ExcludeFiles - return len(patterns) > 0 && harvester.MatchAnyRegexps(patterns, file) + return len(patterns) > 0 && harvester.MatchAny(patterns, file) } // isIgnoreOlder checks if the given state reached ignore_older diff --git a/filebeat/prospector/prospector_log_other_test.go b/filebeat/prospector/prospector_log_other_test.go index 2dd3bba582b..0f7e434edf5 100644 --- a/filebeat/prospector/prospector_log_other_test.go +++ b/filebeat/prospector/prospector_log_other_test.go @@ -3,11 +3,11 @@ package prospector import ( - "regexp" "testing" "github.com/elastic/beats/filebeat/input" "github.com/elastic/beats/filebeat/input/file" + "github.com/elastic/beats/libbeat/common/match" "github.com/stretchr/testify/assert" ) @@ -15,7 +15,7 @@ import ( var matchTests = []struct { file string paths []string - excludeFiles []*regexp.Regexp + excludeFiles []match.Matcher result bool }{ { @@ -45,13 +45,13 @@ var matchTests = []struct { { "test/test.log", []string{"test/*"}, - []*regexp.Regexp{regexp.MustCompile("test.log")}, + []match.Matcher{match.MustCompile("test.log")}, false, }, { "test/test.log", []string{"test/*"}, - []*regexp.Regexp{regexp.MustCompile("test2.log")}, + []match.Matcher{match.MustCompile("test2.log")}, true, }, } diff --git a/filebeat/prospector/prospector_log_windows_test.go b/filebeat/prospector/prospector_log_windows_test.go index a86c4095478..213d35a11af 100644 --- a/filebeat/prospector/prospector_log_windows_test.go +++ b/filebeat/prospector/prospector_log_windows_test.go @@ -1,18 +1,18 @@ -// +build windows +// +build !integration package prospector import ( - "regexp" "testing" + "github.com/elastic/beats/libbeat/common/match" "github.com/stretchr/testify/assert" ) var matchTestsWindows = []struct { file string paths []string - excludeFiles []*regexp.Regexp + excludeFiles []match.Matcher result bool }{ { diff --git a/filebeat/prospector/prospector_test.go b/filebeat/prospector/prospector_test.go index f4ed5460def..d4e95795764 100644 --- a/filebeat/prospector/prospector_test.go +++ b/filebeat/prospector/prospector_test.go @@ -3,12 +3,12 @@ package prospector import ( - "regexp" "testing" - "github.com/elastic/beats/filebeat/input/file" - "github.com/stretchr/testify/assert" + + "github.com/elastic/beats/filebeat/input/file" + "github.com/elastic/beats/libbeat/common/match" ) func TestProspectorInitInputTypeLogError(t *testing.T) { @@ -28,7 +28,7 @@ func TestProspectorFileExclude(t *testing.T) { prospector := Prospector{ config: prospectorConfig{ - ExcludeFiles: []*regexp.Regexp{regexp.MustCompile(`\.gz$`)}, + ExcludeFiles: []match.Matcher{match.MustCompile(`\.gz$`)}, }, } diff --git a/libbeat/common/match/matcher.go b/libbeat/common/match/matcher.go index b848e198135..c9577a2bc16 100644 --- a/libbeat/common/match/matcher.go +++ b/libbeat/common/match/matcher.go @@ -37,6 +37,15 @@ func MustCompileExact(pattern string) ExactMatcher { return m } +// CompileString matches a substring only, the input is not interpreted as +// regular expression +func CompileString(in string) (Matcher, error) { + if in == "" { + return Matcher{(*emptyStringMatcher)(nil)}, nil + } + return Matcher{&substringMatcher{in, []byte(in)}}, nil +} + // Compile regular expression to string matcher. String matcher by default uses // regular expressions as provided by regexp library, but tries to optimize some // common cases, replacing expensive patterns with cheaper custom implementations @@ -93,6 +102,22 @@ func (m *Matcher) Unpack(s string) error { return nil } +func (m *Matcher) MatchAnyString(strs []string) bool { + return matchAnyStrings(m.stringMatcher, strs) +} + +func (m *Matcher) MatchAllStrings(strs []string) bool { + return matchAllStrings(m.stringMatcher, strs) +} + +func (m *ExactMatcher) MatchAnyString(strs []string) bool { + return matchAnyStrings(m.stringMatcher, strs) +} + +func (m *ExactMatcher) MatchAllStrings(strs []string) bool { + return matchAllStrings(m.stringMatcher, strs) +} + func (m *ExactMatcher) Unpack(s string) error { tmp, err := CompileExact(s) if err != nil { @@ -102,3 +127,21 @@ func (m *ExactMatcher) Unpack(s string) error { *m = tmp return nil } + +func matchAnyStrings(m stringMatcher, strs []string) bool { + for _, s := range strs { + if m.MatchString(s) { + return true + } + } + return false +} + +func matchAllStrings(m stringMatcher, strs []string) bool { + for _, s := range strs { + if !m.MatchString(s) { + return false + } + } + return true +} diff --git a/libbeat/processors/condition.go b/libbeat/processors/condition.go index a500c727662..d3e7932ccc9 100644 --- a/libbeat/processors/condition.go +++ b/libbeat/processors/condition.go @@ -1,13 +1,14 @@ package processors import ( + "errors" "fmt" "reflect" - "regexp" "strconv" "strings" "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/common/match" "github.com/elastic/beats/libbeat/logp" ) @@ -24,13 +25,15 @@ type EqualsValue struct { } type Condition struct { - equals map[string]EqualsValue - contains map[string]string - regexp map[string]*regexp.Regexp - rangexp map[string]RangeValue - or []Condition - and []Condition - not *Condition + equals map[string]EqualsValue + matches struct { + name string + filters map[string]match.Matcher + } + rangexp map[string]RangeValue + or []Condition + and []Condition + not *Condition } type WhenProcessor struct { @@ -52,7 +55,6 @@ func NewConditional( } func NewCondition(config *ConditionConfig) (*Condition, error) { - c := Condition{} if config == nil { @@ -60,52 +62,48 @@ func NewCondition(config *ConditionConfig) (*Condition, error) { return nil, nil } - if config.Equals != nil { - if err := c.setEquals(config.Equals); err != nil { - return nil, err - } - } else if config.Contains != nil { - if err := c.setContains(config.Contains); err != nil { - return nil, err - } - } else if config.Regexp != nil { - if err := c.setRegexp(config.Regexp); err != nil { - return nil, err - } - } else if config.Range != nil { - if err := c.setRange(config.Range); err != nil { - return nil, err - } - } else if len(config.OR) > 0 { - for _, condConfig := range config.OR { - cond, err := NewCondition(&condConfig) - if err != nil { - return nil, err - } - c.or = append(c.or, *cond) - } - } else if len(config.AND) > 0 { - for _, condConfig := range config.AND { - cond, err := NewCondition(&condConfig) - if err != nil { - return nil, err - } - c.and = append(c.and, *cond) - } - } else if config.NOT != nil { - cond, err := NewCondition(config.NOT) - if err != nil { - return nil, err - } - c.not = cond - } else { - return nil, fmt.Errorf("missing condition") + var err error + switch { + case config.Equals != nil: + err = c.setEquals(config.Equals) + case config.Contains != nil: + c.matches.name = "contains" + c.matches.filters, err = compileMatches(config.Contains.fields, match.CompileString) + case config.Regexp != nil: + c.matches.name = "regexp" + c.matches.filters, err = compileMatches(config.Regexp.fields, match.Compile) + case config.Range != nil: + err = c.setRange(config.Range) + case len(config.OR) > 0: + c.or, err = NewConditionList(config.OR) + case len(config.AND) > 0: + c.and, err = NewConditionList(config.AND) + case config.NOT != nil: + c.not, err = NewCondition(config.NOT) + default: + err = errors.New("missing condition") + } + if err != nil { + return nil, err } logp.Debug("processors", "New condition %s", c) return &c, nil } +func NewConditionList(config []ConditionConfig) ([]Condition, error) { + out := make([]Condition, len(config)) + for i, condConfig := range config { + cond, err := NewCondition(&condConfig) + if err != nil { + return nil, err + } + + out[i] = *cond + } + return out, nil +} + func (c *Condition) setEquals(cfg *ConditionFields) error { c.equals = map[string]EqualsValue{} @@ -126,40 +124,30 @@ func (c *Condition) setEquals(cfg *ConditionFields) error { return nil } -func (c *Condition) setContains(cfg *ConditionFields) error { - - c.contains = map[string]string{} - - for field, value := range cfg.fields { - switch v := value.(type) { - case string: - c.contains[field] = v - default: - return fmt.Errorf("unexpected type %T of %v", value, value) - } +func compileMatches( + fields map[string]interface{}, + compile func(string) (match.Matcher, error), +) (map[string]match.Matcher, error) { + if len(fields) == 0 { + return nil, nil } - return nil -} + out := map[string]match.Matcher{} + for field, value := range fields { + var err error -func (c *Condition) setRegexp(cfg *ConditionFields) error { - - var err error - - c.regexp = map[string]*regexp.Regexp{} - for field, value := range cfg.fields { switch v := value.(type) { case string: - c.regexp[field], err = regexp.Compile(v) + out[field], err = compile(v) if err != nil { - return err + return nil, err } default: - return fmt.Errorf("unexpected type %T of %v", value, value) + return nil, fmt.Errorf("unexpected type %T of %v", value, value) } } - return nil + return out, nil } func (c *Condition) setRange(cfg *ConditionFields) error { @@ -222,20 +210,9 @@ func (c *Condition) Check(event common.MapStr) bool { return c.checkNOT(event) } - if !c.checkEquals(event) { - return false - } - if !c.checkContains(event) { - return false - } - if !c.checkRegexp(event) { - return false - } - if !c.checkRange(event) { - return false - } - - return true + return c.checkEquals(event) && + c.checkMatches(event) && + c.checkRange(event) } func (c *Condition) checkEquals(event common.MapStr) bool { @@ -268,56 +245,43 @@ func (c *Condition) checkEquals(event common.MapStr) bool { } -func (c *Condition) checkContains(event common.MapStr) bool { -outer: - for field, equalValue := range c.contains { +func (c *Condition) checkMatches(event common.MapStr) bool { + matchers := c.matches.filters + if matchers == nil { + return true + } + + for field, matcher := range matchers { value, err := event.GetValue(field) if err != nil { return false } - switch value.(type) { + switch v := value.(type) { case string: - if !strings.Contains(value.(string), equalValue) { + if !matcher.MatchString(v) { return false } + case []string: - for _, s := range value.([]string) { - if strings.Contains(s, equalValue) { - continue outer - } + if !matcher.MatchAnyString(v) { + return false } - return false - default: - logp.Warn("unexpected type %T in contains condition as it accepts only strings.", value) - return false - } - } - - return true -} - -func (c *Condition) checkRegexp(event common.MapStr) bool { - - for field, equalValue := range c.regexp { - value, err := event.GetValue(field) - if err != nil { - return false - } + default: + str, err := extractString(value) + if err != nil { + logp.Warn("unexpected type %T in %v condition as it accepts only strings.", value, c.matches.name) + return false + } - sValue, err := extractString(value) - if err != nil { - logp.Warn("unexpected type %T in regexp condition as it accepts only strings. ", value) - return false - } - if !equalValue.MatchString(sValue) { - return false + if !matcher.MatchString(str) { + return false + } } } return true - } func (c *Condition) checkRange(event common.MapStr) bool { @@ -420,11 +384,8 @@ func (c Condition) String() string { if len(c.equals) > 0 { s = s + fmt.Sprintf("equals: %v", c.equals) } - if len(c.contains) > 0 { - s = s + fmt.Sprintf("contains: %v", c.contains) - } - if len(c.regexp) > 0 { - s = s + fmt.Sprintf("regexp: %v", c.regexp) + if len(c.matches.filters) > 0 { + s = s + fmt.Sprintf("%v: %v", c.matches.name, c.matches.filters) } if len(c.rangexp) > 0 { s = s + fmt.Sprintf("range: %v", c.rangexp) diff --git a/metricbeat/module/system/process/helper.go b/metricbeat/module/system/process/helper.go index adce7f353ff..e71d0c8d2a3 100644 --- a/metricbeat/module/system/process/helper.go +++ b/metricbeat/module/system/process/helper.go @@ -5,12 +5,12 @@ package process import ( "fmt" "os" - "regexp" "runtime" "strings" "time" "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/common/match" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/module/system" "github.com/elastic/beats/metricbeat/module/system/memory" @@ -40,8 +40,8 @@ type ProcStats struct { CpuTicks bool EnvWhitelist []string - procRegexps []*regexp.Regexp // List of regular expressions used to whitelist processes. - envRegexps []*regexp.Regexp // List of regular expressions used to whitelist env vars. + procRegexps []match.Matcher // List of regular expressions used to whitelist processes. + envRegexps []match.Matcher // List of regular expressions used to whitelist env vars. } // newProcess creates a new Process object and initializes it with process @@ -297,18 +297,18 @@ func (procStats *ProcStats) InitProcStats() error { return nil } - procStats.procRegexps = []*regexp.Regexp{} + procStats.procRegexps = []match.Matcher{} for _, pattern := range procStats.Procs { - reg, err := regexp.Compile(pattern) + reg, err := match.Compile(pattern) if err != nil { return fmt.Errorf("Failed to compile regexp [%s]: %v", pattern, err) } procStats.procRegexps = append(procStats.procRegexps, reg) } - procStats.envRegexps = make([]*regexp.Regexp, 0, len(procStats.EnvWhitelist)) + procStats.envRegexps = make([]match.Matcher, 0, len(procStats.EnvWhitelist)) for _, pattern := range procStats.EnvWhitelist { - reg, err := regexp.Compile(pattern) + reg, err := match.Compile(pattern) if err != nil { return fmt.Errorf("failed to compile env whitelist regexp [%v]: %v", pattern, err) } From e60a0810f68b5d6da017d2a521ffac474fcd4816 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Sun, 29 Jan 2017 09:52:09 -0800 Subject: [PATCH 29/78] Add SSL flags to import_dashboards CLI help (#3487) --- libbeat/docs/newdashboards.asciidoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libbeat/docs/newdashboards.asciidoc b/libbeat/docs/newdashboards.asciidoc index b6a5594e72b..35237669489 100644 --- a/libbeat/docs/newdashboards.asciidoc +++ b/libbeat/docs/newdashboards.asciidoc @@ -151,6 +151,13 @@ The Beat name. The Beat name is required when importing from a zip archive. When the Beat. When running the script from source, the default value is "", so you need to set this option in order to install the index pattern and the dashboards for a single Beat. Otherwise the script imports the index pattern and the dashboards for all Beats. +*`-cacert `*:: +The Certificate Authority to use for server verification. + +*`-cert `*:: +The certificate to use for SSL client authentication. The certificate must be in +PEM format. + *`-dir `*:: Local directory that contains the subdirectories: dashboard, visualization, search, and index-pattern. The default value is the current directory. @@ -164,9 +171,15 @@ Local zip archive with the dashboards. The archive can contain Kibana dashboards You should only use this option if you want to change the index pattern name that's used by default. For example, if the default is `metricbeat-*`, you can change it to `custombeat-*`. +*`-insecure`*:: +If specified, "insecure" SSL connections are allowed. + *`-k `*:: The Elasticsearch index pattern where Kibana saves its configuration. The default value is `.kibana`. +*`-key `*:: +The client certificate key. The key must be in PEM format. + *`-only-dashboards`*:: If specified, then only the dashboards, along with their visualizations and searches, are imported. The index pattern is not imported. By default, this is false. From 8bea9afa1cc252922627af38665ccd00c5c6ebc5 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Mon, 30 Jan 2017 02:05:20 -0800 Subject: [PATCH 30/78] Bump version for xpack doc URL (#3484) --- filebeat/docs/index.asciidoc | 2 +- heartbeat/docs/index.asciidoc | 2 +- libbeat/docs/index.asciidoc | 2 +- metricbeat/docs/index.asciidoc | 2 +- packetbeat/docs/index.asciidoc | 2 +- winlogbeat/docs/index.asciidoc | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/filebeat/docs/index.asciidoc b/filebeat/docs/index.asciidoc index 79839219d85..8d6067f75bc 100644 --- a/filebeat/docs/index.asciidoc +++ b/filebeat/docs/index.asciidoc @@ -8,7 +8,7 @@ include::../../libbeat/docs/version.asciidoc[] :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} :elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :version: {stack-version} :beatname_lc: filebeat :beatname_uc: Filebeat diff --git a/heartbeat/docs/index.asciidoc b/heartbeat/docs/index.asciidoc index 01c48787745..bd5147209ab 100644 --- a/heartbeat/docs/index.asciidoc +++ b/heartbeat/docs/index.asciidoc @@ -8,7 +8,7 @@ include::../../libbeat/docs/version.asciidoc[] :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} :elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :downloads: https://artifacts.elastic.co/downloads/beats :version: {stack-version} :beatname_lc: heartbeat diff --git a/libbeat/docs/index.asciidoc b/libbeat/docs/index.asciidoc index 6e61597ad92..addb03a0104 100644 --- a/libbeat/docs/index.asciidoc +++ b/libbeat/docs/index.asciidoc @@ -7,7 +7,7 @@ include::./version.asciidoc[] :metricbeat: http://www.elastic.co/guide/en/beats/metricbeat/{doc-branch} :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :security: X-Pack Security :ES-version: {stack-version} :LS-version: {stack-version} diff --git a/metricbeat/docs/index.asciidoc b/metricbeat/docs/index.asciidoc index 73fd2bbcf16..e81ceebbc9d 100644 --- a/metricbeat/docs/index.asciidoc +++ b/metricbeat/docs/index.asciidoc @@ -5,7 +5,7 @@ include::../../libbeat/docs/version.asciidoc[] :libbeat: http://www.elastic.co/guide/en/beats/libbeat/{doc-branch} :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :version: {stack-version} :beatname_lc: metricbeat :beatname_uc: Metricbeat diff --git a/packetbeat/docs/index.asciidoc b/packetbeat/docs/index.asciidoc index 97d76267b75..6c6c56aa16c 100644 --- a/packetbeat/docs/index.asciidoc +++ b/packetbeat/docs/index.asciidoc @@ -9,7 +9,7 @@ include::../../libbeat/docs/version.asciidoc[] :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} :elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/{doc-branch} :logstashdoc: https://www.elastic.co/guide/en/logstash/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :kibanadoc: https://www.elastic.co/guide/en/kibana/{doc-branch} :plugindoc: https://www.elastic.co/guide/en/elasticsearch/plugins/{doc-branch} :version: {stack-version} diff --git a/winlogbeat/docs/index.asciidoc b/winlogbeat/docs/index.asciidoc index a477f378223..29bb79c4f80 100644 --- a/winlogbeat/docs/index.asciidoc +++ b/winlogbeat/docs/index.asciidoc @@ -8,7 +8,7 @@ include::../../libbeat/docs/version.asciidoc[] :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} :elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/{doc-branch} -:securitydoc: https://www.elastic.co/guide/en/x-pack/5.0 +:securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :version: {stack-version} :beatname_lc: winlogbeat :beatname_uc: Winlogbeat From c4603f8de56b1ea999f8bdf6a6cf9ee6a3160fee Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Mon, 30 Jan 2017 11:06:32 +0100 Subject: [PATCH 31/78] Rename the syslog module to system (#3475) * Rename the syslog module to system This makes it more similar to Metricbeat and avoids a potential confusion that this acts as a syslog server. OTOH, i renamed the fileset to syslog, because `system.system` seemed weird to me and I don't know of a better name. Part of #3159. * Updated the kibana dashboard --- filebeat/docs/fields.asciidoc | 24 +++--- filebeat/filebeat.template-es2x.json | 4 +- filebeat/filebeat.template.json | 4 +- filebeat/fileset/modules_test.go | 4 +- filebeat/module/syslog/_meta/fields.yml | 10 --- .../darwin-syslog-sample.log-expected.json | 80 ------------------- filebeat/module/system/_meta/fields.yml | 10 +++ .../dashboard/Filebeat-syslog-dashboard.json | 4 +- .../kibana/search/Syslog-system-logs.json | 8 +- .../Syslog-events-by-hostname.json | 4 +- .../Syslog-hostnames-and-processes.json | 4 +- .../system => system/syslog}/_meta/fields.yml | 2 +- .../syslog/config/syslog.yml} | 2 +- .../syslog}/ingest/pipeline.json | 4 +- .../system => system/syslog}/manifest.yml | 2 +- .../syslog}/test/darwin-syslog-sample.log | 0 .../darwin-syslog-sample.log-expected.json | 62 ++++++++++++++ .../syslog}/test/darwin-syslog.log | 0 18 files changed, 105 insertions(+), 123 deletions(-) delete mode 100644 filebeat/module/syslog/_meta/fields.yml delete mode 100644 filebeat/module/syslog/system/test/darwin-syslog-sample.log-expected.json create mode 100644 filebeat/module/system/_meta/fields.yml rename filebeat/module/{syslog => system}/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json (85%) rename filebeat/module/{syslog => system}/_meta/kibana/search/Syslog-system-logs.json (85%) rename filebeat/module/{syslog => system}/_meta/kibana/visualization/Syslog-events-by-hostname.json (93%) rename filebeat/module/{syslog => system}/_meta/kibana/visualization/Syslog-hostnames-and-processes.json (85%) rename filebeat/module/{syslog/system => system/syslog}/_meta/fields.yml (97%) rename filebeat/module/{syslog/system/config/system.yml => system/syslog/config/syslog.yml} (84%) rename filebeat/module/{syslog/system => system/syslog}/ingest/pipeline.json (73%) rename filebeat/module/{syslog/system => system/syslog}/manifest.yml (87%) rename filebeat/module/{syslog/system => system/syslog}/test/darwin-syslog-sample.log (100%) create mode 100644 filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json rename filebeat/module/{syslog/system => system/syslog}/test/darwin-syslog.log (100%) diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index ae8c893d57e..c409d8ef167 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -18,7 +18,7 @@ grouped in the following categories: * <> * <> * <> -* <> +* <> -- [[exported-fields-apache2]] @@ -839,53 +839,53 @@ type: text The error message -[[exported-fields-syslog]] -== Syslog Fields +[[exported-fields-system]] +== System Fields -Module for parsing syslog files. +Module for parsing system log files. [float] -== syslog Fields +== system Fields -Fields from the syslog files. +Fields from the system log files. [float] -== system Fields +== syslog Fields Contains fields from the syslog system logs. [float] -=== syslog.system.timestamp +=== system.syslog.timestamp The timestamp as read from the syslog message. [float] -=== syslog.system.hostname +=== system.syslog.hostname The hostname as read from the syslog message. [float] -=== syslog.system.program +=== system.syslog.program The process name as read from the syslog message. [float] -=== syslog.system.pid +=== system.syslog.pid The PID of the process that sent the syslog message. [float] -=== syslog.system.message +=== system.syslog.message The message in the log line. diff --git a/filebeat/filebeat.template-es2x.json b/filebeat/filebeat.template-es2x.json index 5627941abf8..d2faed3a4d6 100644 --- a/filebeat/filebeat.template-es2x.json +++ b/filebeat/filebeat.template-es2x.json @@ -469,9 +469,9 @@ "index": "not_analyzed", "type": "string" }, - "syslog": { + "system": { "properties": { - "system": { + "syslog": { "properties": { "hostname": { "ignore_above": 1024, diff --git a/filebeat/filebeat.template.json b/filebeat/filebeat.template.json index 6993863dab1..5a9f8d9e3ea 100644 --- a/filebeat/filebeat.template.json +++ b/filebeat/filebeat.template.json @@ -398,9 +398,9 @@ "ignore_above": 1024, "type": "keyword" }, - "syslog": { + "system": { "properties": { - "system": { + "syslog": { "properties": { "hostname": { "ignore_above": 1024, diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index 555014f4b00..a6f5a612ea1 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -27,7 +27,7 @@ func TestNewModuleRegistry(t *testing.T) { configs := []ModuleConfig{ {Module: "nginx"}, {Module: "mysql"}, - {Module: "syslog"}, + {Module: "system"}, } reg, err := newModuleRegistry(modulesPath, configs, nil) @@ -37,7 +37,7 @@ func TestNewModuleRegistry(t *testing.T) { expectedModules := map[string][]string{ "nginx": {"access", "error"}, "mysql": {"slowlog", "error"}, - "syslog": {"system"}, + "system": {"syslog"}, } assert.Equal(t, len(expectedModules), len(reg.registry)) diff --git a/filebeat/module/syslog/_meta/fields.yml b/filebeat/module/syslog/_meta/fields.yml deleted file mode 100644 index 750ba3b11c5..00000000000 --- a/filebeat/module/syslog/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- key: syslog - title: "Syslog" - description: > - Module for parsing syslog files. - fields: - - name: syslog - type: group - description: > - Fields from the syslog files. - fields: diff --git a/filebeat/module/syslog/system/test/darwin-syslog-sample.log-expected.json b/filebeat/module/syslog/system/test/darwin-syslog-sample.log-expected.json deleted file mode 100644 index 1a41743ce63..00000000000 --- a/filebeat/module/syslog/system/test/darwin-syslog-sample.log-expected.json +++ /dev/null @@ -1,80 +0,0 @@ -[ -{ - "_index": "filebeat-2016.12.15", - "_type": "log", - "_id": "AVkBzqQ6j7PoDSoX1nl9", - "_score": null, - "_source": { - "@timestamp": "2016-12-13T11:35:28.000Z", - "offset": 907, - "beat": { - "hostname": "a-mac-with-esc-key.local", - "name": "a-mac-with-esc-key.local", - "version": "6.0.0-alpha1" - }, - "input_type": "log", - "source": "module/syslog/system/test/darwin-syslog-sample.log", - "syslog": { - "system": { - "hostname": "a-mac-with-esc-key", - "pid": "21412", - "program": "GoogleSoftwareUpdateAgent", - "message": "2016-12-13 11:35:28.420 GoogleSoftwareUpdateAgent[21412/0x700007399000] [lvl=2] -[KSAgentApp updateProductWithProductID:usingEngine:] Checking for updates for \"All Products\" using engine \n\t\t>>\n\t\tprocessor=\n\t\t\tisProcessing=NO actionsCompleted=0 progress=0.00\n\t\t\terrors=0 currentActionErrors=0\n\t\t\tevents=0 currentActionEvents=0\n\t\t\tactionQueue=( )\n\t\t>\n\t\tdelegate=(null)\n\t\tserverInfoStore=(null)\n\t\terrors=0\n\t>", - "timestamp": "Dec 13 11:35:28" - } - }, - "fields": { - "pipeline_id": "syslog-system-pipeline", - "source_type": "syslog-system" - }, - "type": "log" - }, - "fields": { - "@timestamp": [ - 1481628928000 - ] - }, - "sort": [ - 1481628928000 - ] -}, -{ - "_index": "filebeat-2016.12.15", - "_type": "log", - "_id": "AVkBzrcuj7PoDSoX1nl-", - "_score": null, - "_source": { - "@timestamp": "2016-12-13T11:35:28.000Z", - "offset": 1176, - "beat": { - "hostname": "a-mac-with-esc-key.local", - "name": "a-mac-with-esc-key.local", - "version": "6.0.0-alpha1" - }, - "input_type": "log", - "source": "module/syslog/system/test/darwin-syslog-sample.log", - "syslog": { - "system": { - "hostname": "a-mac-with-esc-key", - "pid": "21412", - "program": "GoogleSoftwareUpdateAgent", - "message": "2016-12-13 11:35:28.421 GoogleSoftwareUpdateAgent[21412/0x700007399000] [lvl=2] -[KSUpdateEngine updateAllExceptProduct:] KSUpdateEngine updating all installed products, except:'com.google.Keystone'.", - "timestamp": "Dec 13 11:35:28" - } - }, - "fields": { - "pipeline_id": "syslog-system-pipeline", - "source_type": "syslog-system" - }, - "type": "log" - }, - "fields": { - "@timestamp": [ - 1481628928000 - ] - }, - "sort": [ - 1481628928000 - ] -} -] diff --git a/filebeat/module/system/_meta/fields.yml b/filebeat/module/system/_meta/fields.yml new file mode 100644 index 00000000000..d4c85fa85ec --- /dev/null +++ b/filebeat/module/system/_meta/fields.yml @@ -0,0 +1,10 @@ +- key: system + title: "System" + description: > + Module for parsing system log files. + fields: + - name: system + type: group + description: > + Fields from the system log files. + fields: diff --git a/filebeat/module/syslog/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json b/filebeat/module/system/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json similarity index 85% rename from filebeat/module/syslog/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json rename to filebeat/module/system/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json index 7275bf782d2..dca2e3e85f6 100644 --- a/filebeat/module/syslog/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json +++ b/filebeat/module/system/_meta/kibana/dashboard/Filebeat-syslog-dashboard.json @@ -4,10 +4,10 @@ "description": "", "title": "Filebeat syslog dashboard", "uiStateJSON": "{}", - "panelsJSON": "[{\"id\":\"Syslog-events-by-hostname\",\"type\":\"visualization\",\"panelIndex\":1,\"size_x\":8,\"size_y\":4,\"col\":1,\"row\":1},{\"id\":\"Syslog-hostnames-and-processes\",\"type\":\"visualization\",\"panelIndex\":2,\"size_x\":4,\"size_y\":4,\"col\":9,\"row\":1},{\"id\":\"Syslog-system-logs\",\"type\":\"search\",\"panelIndex\":3,\"size_x\":12,\"size_y\":7,\"col\":1,\"row\":5,\"columns\":[\"syslog.system.hostname\",\"syslog.system.program\",\"syslog.system.message\"],\"sort\":[\"@timestamp\",\"desc\"]}]", + "panelsJSON": "[{\"id\":\"Syslog-events-by-hostname\",\"type\":\"visualization\",\"panelIndex\":1,\"size_x\":8,\"size_y\":4,\"col\":1,\"row\":1},{\"id\":\"Syslog-hostnames-and-processes\",\"type\":\"visualization\",\"panelIndex\":2,\"size_x\":4,\"size_y\":4,\"col\":9,\"row\":1},{\"id\":\"Syslog-system-logs\",\"type\":\"search\",\"panelIndex\":3,\"size_x\":12,\"size_y\":7,\"col\":1,\"row\":5,\"columns\":[\"system.syslog.hostname\",\"system.syslog.program\",\"system.syslog.message\"],\"sort\":[\"@timestamp\",\"desc\"]}]", "optionsJSON": "{\"darkTheme\":false}", "version": 1, "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}}}]}" } -} \ No newline at end of file +} diff --git a/filebeat/module/syslog/_meta/kibana/search/Syslog-system-logs.json b/filebeat/module/system/_meta/kibana/search/Syslog-system-logs.json similarity index 85% rename from filebeat/module/syslog/_meta/kibana/search/Syslog-system-logs.json rename to filebeat/module/system/_meta/kibana/search/Syslog-system-logs.json index e8aa56091af..7ca969c18df 100644 --- a/filebeat/module/syslog/_meta/kibana/search/Syslog-system-logs.json +++ b/filebeat/module/system/_meta/kibana/search/Syslog-system-logs.json @@ -11,8 +11,8 @@ "searchSourceJSON": "{\"index\":\"filebeat-*\",\"filter\":[],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647},\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}}}" }, "columns": [ - "syslog.system.hostname", - "syslog.system.program", - "syslog.system.message" + "system.syslog.hostname", + "system.syslog.program", + "system.syslog.message" ] -} \ No newline at end of file +} diff --git a/filebeat/module/syslog/_meta/kibana/visualization/Syslog-events-by-hostname.json b/filebeat/module/system/_meta/kibana/visualization/Syslog-events-by-hostname.json similarity index 93% rename from filebeat/module/syslog/_meta/kibana/visualization/Syslog-events-by-hostname.json rename to filebeat/module/system/_meta/kibana/visualization/Syslog-events-by-hostname.json index bcdef77dede..54f616c36d4 100644 --- a/filebeat/module/syslog/_meta/kibana/visualization/Syslog-events-by-hostname.json +++ b/filebeat/module/system/_meta/kibana/visualization/Syslog-events-by-hostname.json @@ -1,5 +1,5 @@ { - "visState": "{\"title\":\"Syslog events by hostname\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"syslog.system.hostname\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "visState": "{\"title\":\"Syslog events by hostname\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"system.syslog.hostname\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", "description": "", "title": "Syslog events by hostname", "uiStateJSON": "{}", @@ -8,4 +8,4 @@ "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[]}" } -} \ No newline at end of file +} diff --git a/filebeat/module/syslog/_meta/kibana/visualization/Syslog-hostnames-and-processes.json b/filebeat/module/system/_meta/kibana/visualization/Syslog-hostnames-and-processes.json similarity index 85% rename from filebeat/module/syslog/_meta/kibana/visualization/Syslog-hostnames-and-processes.json rename to filebeat/module/system/_meta/kibana/visualization/Syslog-hostnames-and-processes.json index 38f3fcae528..c3fc33af828 100644 --- a/filebeat/module/syslog/_meta/kibana/visualization/Syslog-hostnames-and-processes.json +++ b/filebeat/module/system/_meta/kibana/visualization/Syslog-hostnames-and-processes.json @@ -1,5 +1,5 @@ { - "visState": "{\"title\":\"Syslog hostnames and processes\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"bottom\",\"isDonut\":true},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"syslog.system.hostname\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"syslog.system.program\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "visState": "{\"title\":\"Syslog hostnames and processes\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"bottom\",\"isDonut\":true},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"system.syslog.hostname\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"system.syslog.program\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", "description": "", "title": "Syslog hostnames and processes", "uiStateJSON": "{}", @@ -8,4 +8,4 @@ "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[]}" } -} \ No newline at end of file +} diff --git a/filebeat/module/syslog/system/_meta/fields.yml b/filebeat/module/system/syslog/_meta/fields.yml similarity index 97% rename from filebeat/module/syslog/system/_meta/fields.yml rename to filebeat/module/system/syslog/_meta/fields.yml index 92174268a0a..5dd3d0e4f83 100644 --- a/filebeat/module/syslog/system/_meta/fields.yml +++ b/filebeat/module/system/syslog/_meta/fields.yml @@ -1,4 +1,4 @@ -- name: system +- name: syslog type: group description: > Contains fields from the syslog system logs. diff --git a/filebeat/module/syslog/system/config/system.yml b/filebeat/module/system/syslog/config/syslog.yml similarity index 84% rename from filebeat/module/syslog/system/config/system.yml rename to filebeat/module/system/syslog/config/syslog.yml index dfea10dc28d..65338c8da0c 100644 --- a/filebeat/module/syslog/system/config/system.yml +++ b/filebeat/module/system/syslog/config/syslog.yml @@ -8,4 +8,4 @@ multiline: pattern: "^\\s" match: after fields: - source_type: syslog-system + source_type: system-syslog diff --git a/filebeat/module/syslog/system/ingest/pipeline.json b/filebeat/module/system/syslog/ingest/pipeline.json similarity index 73% rename from filebeat/module/syslog/system/ingest/pipeline.json rename to filebeat/module/system/syslog/ingest/pipeline.json index aaa086c4529..b6f099c93a9 100644 --- a/filebeat/module/syslog/system/ingest/pipeline.json +++ b/filebeat/module/system/syslog/ingest/pipeline.json @@ -5,7 +5,7 @@ "grok": { "field": "message", "patterns": [ - "%{SYSLOGTIMESTAMP:syslog.system.timestamp} %{SYSLOGHOST:syslog.system.hostname} %{DATA:syslog.system.program}(?:\\[%{POSINT:syslog.system.pid}\\])?: %{GREEDYMULTILINE:syslog.system.message}" + "%{SYSLOGTIMESTAMP:system.syslog.timestamp} %{SYSLOGHOST:system.syslog.hostname} %{DATA:system.syslog.program}(?:\\[%{POSINT:system.syslog.pid}\\])?: %{GREEDYMULTILINE:system.syslog.message}" ], "pattern_definitions" : { "GREEDYMULTILINE" : "(.|\n)*" @@ -20,7 +20,7 @@ }, { "date": { - "field": "syslog.system.timestamp", + "field": "system.syslog.timestamp", "target_field": "@timestamp", "formats": [ "MMM d HH:mm:ss", diff --git a/filebeat/module/syslog/system/manifest.yml b/filebeat/module/system/syslog/manifest.yml similarity index 87% rename from filebeat/module/syslog/system/manifest.yml rename to filebeat/module/system/syslog/manifest.yml index e75413ed3bf..bd2ae28b40f 100644 --- a/filebeat/module/syslog/system/manifest.yml +++ b/filebeat/module/system/syslog/manifest.yml @@ -10,4 +10,4 @@ var: os.windows: [] ingest_pipeline: ingest/pipeline.json -prospector: config/system.yml +prospector: config/syslog.yml diff --git a/filebeat/module/syslog/system/test/darwin-syslog-sample.log b/filebeat/module/system/syslog/test/darwin-syslog-sample.log similarity index 100% rename from filebeat/module/syslog/system/test/darwin-syslog-sample.log rename to filebeat/module/system/syslog/test/darwin-syslog-sample.log diff --git a/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json b/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json new file mode 100644 index 00000000000..5bb4fa923c4 --- /dev/null +++ b/filebeat/module/system/syslog/test/darwin-syslog-sample.log-expected.json @@ -0,0 +1,62 @@ +[ + { + "_index" : "filebeat-2017.01.27", + "_type" : "log", + "_id" : "AVngMBcpUmQau8zc_UuM", + "_score" : 1.0, + "_source" : { + "@timestamp" : "2017-12-13T11:35:28.000Z", + "system" : { + "syslog" : { + "hostname" : "a-mac-with-esc-key", + "pid" : "21412", + "program" : "GoogleSoftwareUpdateAgent", + "message" : "2016-12-13 11:35:28.420 GoogleSoftwareUpdateAgent[21412/0x700007399000] [lvl=2] -[KSAgentApp updateProductWithProductID:usingEngine:] Checking for updates for \"All Products\" using engine \n\t\t>>\n\t\tprocessor=\n\t\t\tisProcessing=NO actionsCompleted=0 progress=0.00\n\t\t\terrors=0 currentActionErrors=0\n\t\t\tevents=0 currentActionEvents=0\n\t\t\tactionQueue=( )\n\t\t>\n\t\tdelegate=(null)\n\t\tserverInfoStore=(null)\n\t\terrors=0\n\t>", + "timestamp" : "Dec 13 11:35:28" + } + }, + "offset" : 907, + "beat" : { + "hostname" : "a-mac-with-esc-key.local", + "name" : "a-mac-with-esc-key.local", + "version" : "6.0.0-alpha1" + }, + "input_type" : "log", + "source" : "module/system/syslog/test/darwin-syslog-sample.log", + "fields" : { + "source_type" : "system-syslog" + }, + "type" : "log" + } + }, + { + "_index" : "filebeat-2017.01.27", + "_type" : "log", + "_id" : "AVngMCdNUmQau8zc_UuN", + "_score" : 1.0, + "_source" : { + "@timestamp" : "2017-12-13T11:35:28.000Z", + "system" : { + "syslog" : { + "hostname" : "a-mac-with-esc-key", + "pid" : "21412", + "program" : "GoogleSoftwareUpdateAgent", + "message" : "2016-12-13 11:35:28.421 GoogleSoftwareUpdateAgent[21412/0x700007399000] [lvl=2] -[KSUpdateEngine updateAllExceptProduct:] KSUpdateEngine updating all installed products, except:'com.google.Keystone'.", + "timestamp" : "Dec 13 11:35:28" + } + }, + "offset" : 1176, + "beat" : { + "hostname" : "a-mac-with-esc-key.local", + "name" : "a-mac-with-esc-key.local", + "version" : "6.0.0-alpha1" + }, + "input_type" : "log", + "source" : "module/system/syslog/test/darwin-syslog-sample.log", + "fields" : { + "source_type" : "system-syslog" + }, + "type" : "log" + } + } +] diff --git a/filebeat/module/syslog/system/test/darwin-syslog.log b/filebeat/module/system/syslog/test/darwin-syslog.log similarity index 100% rename from filebeat/module/syslog/system/test/darwin-syslog.log rename to filebeat/module/system/syslog/test/darwin-syslog.log From 9fd937f706bde64133faba026e892b4f67c15906 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Tue, 31 Jan 2017 01:44:19 -0800 Subject: [PATCH 32/78] Add heartbeat to list of beats in platform ref (#3483) --- libbeat/docs/index.asciidoc | 1 + libbeat/docs/installing-beats.asciidoc | 1 + 2 files changed, 2 insertions(+) diff --git a/libbeat/docs/index.asciidoc b/libbeat/docs/index.asciidoc index addb03a0104..6891330e0f5 100644 --- a/libbeat/docs/index.asciidoc +++ b/libbeat/docs/index.asciidoc @@ -7,6 +7,7 @@ include::./version.asciidoc[] :metricbeat: http://www.elastic.co/guide/en/beats/metricbeat/{doc-branch} :filebeat: http://www.elastic.co/guide/en/beats/filebeat/{doc-branch} :winlogbeat: http://www.elastic.co/guide/en/beats/winlogbeat/{doc-branch} +:heartbeat: http://www.elastic.co/guide/en/beats/heartbeat/{doc-branch} :securitydoc: https://www.elastic.co/guide/en/x-pack/5.2 :security: X-Pack Security :ES-version: {stack-version} diff --git a/libbeat/docs/installing-beats.asciidoc b/libbeat/docs/installing-beats.asciidoc index 96b2f574da5..6d99e75e0a1 100644 --- a/libbeat/docs/installing-beats.asciidoc +++ b/libbeat/docs/installing-beats.asciidoc @@ -25,5 +25,6 @@ Each Beat is a separately installable product. To get up and running quickly wit * {metricbeat}/metricbeat-getting-started.html[Metricbeat] * {filebeat}/filebeat-getting-started.html[Filebeat] * {winlogbeat}/winlogbeat-getting-started.html[Winlogbeat] +* {heartbeat}/heartbeat-getting-started.html[Heartbeat] From 125075b3ad31a512474c5bea801138c32442e8f3 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 31 Jan 2017 10:45:53 +0100 Subject: [PATCH 33/78] Gather module configuration fragments (#3488) Added configuration samples for the modules and added rules to gather them in the short and full versions of the configs. This leaves all modules commented out, so there isn't any change in the default behaviour, just the modules added to the configuration files. Part of #3159. Also removes `_meta/beat.yml` and `_meta/beat.full.yml` temporary generated files from git. --- filebeat/.gitignore | 2 + filebeat/Makefile | 12 +- filebeat/_meta/common.full.p1.yml | 9 + .../{beat.full.yml => common.full.p2.yml} | 9 - filebeat/_meta/common.p1.yml | 9 + filebeat/_meta/{beat.yml => common.p2.yml} | 10 +- filebeat/filebeat.full.yml | 109 ++++++- filebeat/filebeat.yml | 21 ++ filebeat/module/apache2/_meta/config.full.yml | 29 ++ filebeat/module/mysql/_meta/config.full.yml | 24 ++ filebeat/module/mysql/_meta/config.yml | 1 + filebeat/module/nginx/_meta/config.full.yml | 24 +- filebeat/module/nginx/_meta/config.yml | 4 + filebeat/module/system/_meta/config.full.yml | 12 + filebeat/module/system/_meta/config.yml | 1 + metricbeat/.gitignore | 2 + metricbeat/_meta/beat.full.yml | 294 ------------------ metricbeat/_meta/beat.yml | 49 --- 18 files changed, 245 insertions(+), 376 deletions(-) create mode 100644 filebeat/_meta/common.full.p1.yml rename filebeat/_meta/{beat.full.yml => common.full.p2.yml} (96%) create mode 100644 filebeat/_meta/common.p1.yml rename filebeat/_meta/{beat.yml => common.p2.yml} (81%) create mode 100644 filebeat/module/apache2/_meta/config.full.yml create mode 100644 filebeat/module/mysql/_meta/config.full.yml create mode 100644 filebeat/module/mysql/_meta/config.yml create mode 100644 filebeat/module/system/_meta/config.full.yml create mode 100644 filebeat/module/system/_meta/config.yml delete mode 100644 metricbeat/_meta/beat.full.yml delete mode 100644 metricbeat/_meta/beat.yml diff --git a/filebeat/.gitignore b/filebeat/.gitignore index 3b6a7232e74..eb13d03ca1e 100644 --- a/filebeat/.gitignore +++ b/filebeat/.gitignore @@ -7,4 +7,6 @@ filebeat build _meta/kibana _meta/module.generated +_meta/beat.yml +_meta/beat.full.yml /tests/load/logs diff --git a/filebeat/Makefile b/filebeat/Makefile index 6ac7d0da625..279a3d2ad66 100644 --- a/filebeat/Makefile +++ b/filebeat/Makefile @@ -34,9 +34,19 @@ modules: rm -rf _meta/module.generated rsync -av module/ _meta/module.generated --exclude "_meta" --exclude "*/*/test" +# Collects all module configs +.PHONY: configs +configs: python-env + cat ${ES_BEATS}/filebeat/_meta/common.p1.yml > _meta/beat.yml + . ${PYTHON_ENV}/bin/activate; python ${ES_BEATS}/metricbeat/scripts/config_collector.py --beat ${BEAT_NAME} $(PWD) >> _meta/beat.yml + cat ${ES_BEATS}/filebeat/_meta/common.p2.yml >> _meta/beat.yml + cat ${ES_BEATS}/filebeat/_meta/common.full.p1.yml > _meta/beat.full.yml + . ${PYTHON_ENV}/bin/activate; python ${ES_BEATS}/metricbeat/scripts/config_collector.py --beat ${BEAT_NAME} --full $(PWD) >> _meta/beat.full.yml + cat ${ES_BEATS}/filebeat/_meta/common.full.p2.yml >> _meta/beat.full.yml + # Runs all collection steps and updates afterwards .PHONY: collect -collect: fields kibana modules +collect: fields kibana modules configs # Creates a new fileset. Requires the params MODULE and FILESET diff --git a/filebeat/_meta/common.full.p1.yml b/filebeat/_meta/common.full.p1.yml new file mode 100644 index 00000000000..c02e11deacb --- /dev/null +++ b/filebeat/_meta/common.full.p1.yml @@ -0,0 +1,9 @@ +######################## Filebeat Configuration ############################ + +# This file is a full configuration example documenting all non-deprecated +# options in comments. For a shorter configuration example, that contains only +# the most common options, please see filebeat.yml in the same directory. +# +# You can find the full configuration reference here: +# https://www.elastic.co/guide/en/beats/filebeat/index.html + diff --git a/filebeat/_meta/beat.full.yml b/filebeat/_meta/common.full.p2.yml similarity index 96% rename from filebeat/_meta/beat.full.yml rename to filebeat/_meta/common.full.p2.yml index fc9c0f19741..f766b74dcc4 100644 --- a/filebeat/_meta/beat.full.yml +++ b/filebeat/_meta/common.full.p2.yml @@ -1,12 +1,3 @@ -##################$$$###### Filebeat Configuration ############################ - -# This file is a full configuration example documenting all non-deprecated -# options in comments. For a shorter configuration example, that contains only -# the most common options, please see filebeat.yml in the same directory. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/filebeat/index.html - #=========================== Filebeat prospectors ============================= # List of prospectors to fetch data. diff --git a/filebeat/_meta/common.p1.yml b/filebeat/_meta/common.p1.yml new file mode 100644 index 00000000000..5bbabd35dd5 --- /dev/null +++ b/filebeat/_meta/common.p1.yml @@ -0,0 +1,9 @@ +###################### Filebeat Configuration Example ######################### + +# This file is an example configuration file highlighting only the most common +# options. The filebeat.full.yml file from the same directory contains all the +# supported options with more comments. You can use it as a reference. +# +# You can find the full configuration reference here: +# https://www.elastic.co/guide/en/beats/filebeat/index.html + diff --git a/filebeat/_meta/beat.yml b/filebeat/_meta/common.p2.yml similarity index 81% rename from filebeat/_meta/beat.yml rename to filebeat/_meta/common.p2.yml index 8ac24e02c7e..19de5fd7373 100644 --- a/filebeat/_meta/beat.yml +++ b/filebeat/_meta/common.p2.yml @@ -1,11 +1,5 @@ -###################### Filebeat Configuration Example ######################### - -# This file is an example configuration file highlighting only the most common -# options. The filebeat.full.yml file from the same directory contains all the -# supported options with more comments. You can use it as a reference. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/filebeat/index.html +# For more available modules and options, please see the filebeat.full.yml sample +# configuration file. #=========================== Filebeat prospectors ============================= diff --git a/filebeat/filebeat.full.yml b/filebeat/filebeat.full.yml index fd6c2744366..8b35c37ebab 100644 --- a/filebeat/filebeat.full.yml +++ b/filebeat/filebeat.full.yml @@ -1,4 +1,4 @@ -##################$$$###### Filebeat Configuration ############################ +######################## Filebeat Configuration ############################ # This file is a full configuration example documenting all non-deprecated # options in comments. For a shorter configuration example, that contains only @@ -7,6 +7,113 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/filebeat/index.html + +#========================== Modules configuration ============================ +filebeat.modules: + +#------------------------------- System Module ------------------------------- +#- module: system + # Syslog + #syslog: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + +#------------------------------- Apache2 Module ------------------------------ +#- module: apache2 + # Access logs + #access: + #enabled: true + + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. + #var.pipeline: with_plugins + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + # Error logs + #error: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + +#-------------------------------- MySQL Module ------------------------------- +#- module: mysql + # Error logs + #error: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + # Slow logs + #slowlog: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + +#-------------------------------- Nginx Module ------------------------------- +#- module: nginx + # Access logs + #access: + #enabled: true + + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. + #var.pipeline: with_plugins + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + # Error logs + #error: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + #=========================== Filebeat prospectors ============================= # List of prospectors to fetch data. diff --git a/filebeat/filebeat.yml b/filebeat/filebeat.yml index 8d1e7f9eba9..fca5fd57802 100644 --- a/filebeat/filebeat.yml +++ b/filebeat/filebeat.yml @@ -7,6 +7,27 @@ # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/filebeat/index.html + +#========================== Modules configuration ============================ +filebeat.modules: + +#------------------------------- System Module ------------------------------- +#- module: system + +#-------------------------------- MySQL Module ------------------------------- +#- module: mysql + +#-------------------------------- Nginx Module ------------------------------- +#- module: nginx + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. + #access.var.pipeline: with_plugins + + +# For more available modules and options, please see the filebeat.full.yml sample +# configuration file. + #=========================== Filebeat prospectors ============================= filebeat.prospectors: diff --git a/filebeat/module/apache2/_meta/config.full.yml b/filebeat/module/apache2/_meta/config.full.yml new file mode 100644 index 00000000000..44481fee9a8 --- /dev/null +++ b/filebeat/module/apache2/_meta/config.full.yml @@ -0,0 +1,29 @@ +#- module: apache2 + # Access logs + #access: + #enabled: true + + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. + #var.pipeline: with_plugins + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + # Error logs + #error: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: diff --git a/filebeat/module/mysql/_meta/config.full.yml b/filebeat/module/mysql/_meta/config.full.yml new file mode 100644 index 00000000000..f3b4725534b --- /dev/null +++ b/filebeat/module/mysql/_meta/config.full.yml @@ -0,0 +1,24 @@ +#- module: mysql + # Error logs + #error: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: + + # Slow logs + #slowlog: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: diff --git a/filebeat/module/mysql/_meta/config.yml b/filebeat/module/mysql/_meta/config.yml new file mode 100644 index 00000000000..22942e984f0 --- /dev/null +++ b/filebeat/module/mysql/_meta/config.yml @@ -0,0 +1 @@ +#- module: mysql diff --git a/filebeat/module/nginx/_meta/config.full.yml b/filebeat/module/nginx/_meta/config.full.yml index 83f5d3d4b3d..f80f92dbc3b 100644 --- a/filebeat/module/nginx/_meta/config.full.yml +++ b/filebeat/module/nginx/_meta/config.full.yml @@ -1,33 +1,29 @@ -- module: nginx +#- module: nginx # Access logs - filesets.access: - enabled: true + #access: + #enabled: true - # Format. Options are with_plugins and no_plugins + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. #var.pipeline: with_plugins # Set custom paths for the log files. If left empty, - # Filebeat choose the path depending on your OS. + # Filebeat will choose the paths depending on your OS. #var.paths: - # Use a custom Ingest Node pipeline file (advanced). - #ingest_pipeline: - # Prospector configuration (advanced). Any prospector configuration option # can be added under this section. #prospector: # Error logs - filesets.error: - enabled: true + #error: + #enabled: true # Set custom paths for the log files. If left empty, - # Filebeat choose the path depending on your OS. + # Filebeat will choose the paths depending on your OS. #var.paths: - # Use a custom Ingest Node pipeline file (advanced). - #ingest_pipeline: - # Prospector configuration (advanced). Any prospector configuration option # can be added under this section. #prospector: diff --git a/filebeat/module/nginx/_meta/config.yml b/filebeat/module/nginx/_meta/config.yml index 153c32978d7..ae1f0dd933d 100644 --- a/filebeat/module/nginx/_meta/config.yml +++ b/filebeat/module/nginx/_meta/config.yml @@ -1 +1,5 @@ #- module: nginx + # Ingest Node pipeline to use. Options are `with_plugins` (default) + # and `no_plugins`. Use `no_plugins` if you don't have the geoip or + # the user agent Node ingest plugins installed. + #access.var.pipeline: with_plugins diff --git a/filebeat/module/system/_meta/config.full.yml b/filebeat/module/system/_meta/config.full.yml new file mode 100644 index 00000000000..eef3fa9aaf2 --- /dev/null +++ b/filebeat/module/system/_meta/config.full.yml @@ -0,0 +1,12 @@ +#- module: system + # Syslog + #syslog: + #enabled: true + + # Set custom paths for the log files. If left empty, + # Filebeat will choose the paths depending on your OS. + #var.paths: + + # Prospector configuration (advanced). Any prospector configuration option + # can be added under this section. + #prospector: diff --git a/filebeat/module/system/_meta/config.yml b/filebeat/module/system/_meta/config.yml new file mode 100644 index 00000000000..010586ba9b4 --- /dev/null +++ b/filebeat/module/system/_meta/config.yml @@ -0,0 +1 @@ +#- module: system diff --git a/metricbeat/.gitignore b/metricbeat/.gitignore index 32c8135a353..91c5cac653c 100644 --- a/metricbeat/.gitignore +++ b/metricbeat/.gitignore @@ -1,5 +1,7 @@ build _meta/kibana +_meta/beat.yml +_meta.beat.full.yml /metricbeat /metricbeat.test diff --git a/metricbeat/_meta/beat.full.yml b/metricbeat/_meta/beat.full.yml deleted file mode 100644 index 8456c095de1..00000000000 --- a/metricbeat/_meta/beat.full.yml +++ /dev/null @@ -1,294 +0,0 @@ -########################## Metricbeat Configuration ########################### - -# This file is a full configuration example documenting all non-deprecated -# options in comments. For a shorter configuration example, that contains only -# the most common options, please see metricbeat.yml in the same directory. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/metricbeat/index.html - -#============================ Config Reloading =============================== - -# Config reloading allows to dynamically load modules. Each file which is -# monitored must contain one or multiple modules as a list. -metricbeat.modules.reload: - - # Glob pattern for configuration reloading - path: ${path.config}/conf.d/*.yml - - # Period on which files under path should be checked for chagnes - period: 10s - - # Set to true to enable config reloading - enabled: false - -#========================== Modules configuration ============================ -metricbeat.modules: - -#------------------------------- System Module ------------------------------- -- module: system - metricsets: - # CPU stats - - cpu - - # System Load stats - - load - - # Per CPU core stats - #- core - - # IO stats - #- diskio - - # Per filesystem stats - - filesystem - - # File system summary stats - - fsstat - - # Memory stats - - memory - - # Network stats - - network - - # Per process stats - - process - - # Sockets and connection info (linux only) - #- socket - enabled: true - period: 10s - processes: ['.*'] - - # if true, exports the CPU usage in ticks, together with the percentage values - #cpu_ticks: false - - # EXPERIMENTAL: cgroups can be enabled for the process metricset. - #cgroups: false - - # A list of regular expressions used to whitelist environment variables - # reported with the process metricset's events. Defaults to empty. - #process.env.whitelist: [] - - # Configure reverse DNS lookup on remote IP addresses in the socket metricset. - #socket.reverse_lookup.enabled: false - #socket.reverse_lookup.success_ttl: 60s - #socket.reverse_lookup.failure_ttl: 60s - -#------------------------------- Apache Module ------------------------------- -#- module: apache - #metricsets: ["status"] - #enabled: true - #period: 10s - - # Apache hosts - #hosts: ["http://127.0.0.1"] - - # Path to server status. Default server-status - #server_status_path: "server-status" - - # Username of hosts. Empty by default - #username: test - - # Password of hosts. Empty by default - #password: test123 - -#-------------------------------- ceph Module -------------------------------- -#- module: ceph -# metricsets: ["health"] -# enabled: true -# period: 10s -# hosts: ["localhost:5000"] - -#------------------------------ couchbase Module ----------------------------- -#- module: couchbase - #metricsets: ["cluster", "node", "bucket"] - #enabled: true - #period: 10s - #hosts: ["localhost:8091"] - -#------------------------------- Docker Module ------------------------------- -#- module: docker - #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] - #hosts: ["unix:///var/run/docker.sock"] - #enabled: true - #period: 10s - - # To connect to Docker over TLS you must specify a client and CA certificate. - #ssl: - #certificate_authority: "/etc/pki/root/ca.pem" - #certificate: "/etc/pki/client/cert.pem" - #key: "/etc/pki/client/cert.key" - -#------------------------------- HAProxy Module ------------------------------ -#- module: haproxy - #metricsets: ["info", "stat"] - #enabled: true - #period: 10s - #hosts: ["tcp://127.0.0.1:14567"] - -#-------------------------------- kafka Module ------------------------------- -#- module: kafka - #metricsets: ["partition"] - #enabled: true - #period: 10s - #hosts: ["localhost:9092"] - - #client_id: metricbeat - #retries: 3 - #backoff: 250ms - - # List of Topics to query metadata for. If empty, all topics will be queried. - #topics: [] - - # Optional SSL. By default is off. - # List of root certificates for HTTPS server verifications - #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] - - # Certificate for SSL client authentication - #ssl.certificate: "/etc/pki/client/cert.pem" - - # Client Certificate Key - #ssl.key: "/etc/pki/client/cert.key" - - # SASL authentication - #username: "" - #password: "" - -#------------------------------- MongoDB Module ------------------------------ -#- module: mongodb - #metricsets: ["dbstats", "status"] - #enabled: true - #period: 10s - - # The hosts must be passed as MongoDB URLs in the format: - # [mongodb://][user:pass@]host[:port]. - # The username and password can also be set using the respective configuration - # options. The credentials in the URL take precedence over the username and - # password configuration options. - #hosts: ["localhost:27017"] - - # Username to use when connecting to MongoDB. Empty by default. - #username: user - - # Password to use when connecting to MongoDB. Empty by default. - #password: pass - -#-------------------------------- MySQL Module ------------------------------- -#- module: mysql - #metricsets: ["status"] - #enabled: true - #period: 10s - - # Host DSN should be defined as "user:pass@tcp(127.0.0.1:3306)/" - # The username and password can either be set in the DSN or using the username - # and password config options. Those specified in the DSN take precedence. - #hosts: ["root:secret@tcp(127.0.0.1:3306)/"] - - # Username of hosts. Empty by default. - #username: root - - # Password of hosts. Empty by default. - #password: secret - - # By setting raw to true, all raw fields from the status metricset will be added to the event. - #raw: false - -#-------------------------------- Nginx Module ------------------------------- -#- module: nginx - #metricsets: ["stubstatus"] - #enabled: true - #period: 10s - - # Nginx hosts - #hosts: ["http://127.0.0.1"] - - # Path to server status. Default server-status - #server_status_path: "server-status" - -#------------------------------- php_fpm Module ------------------------------ -#- module: php_fpm - #metricsets: ["pool"] - #enabled: true - #period: 10s - #status_path: "/status" - #hosts: ["localhost:8080"] - -#----------------------------- PostgreSQL Module ----------------------------- -#- module: postgresql - #metricsets: - # Stats about every PostgreSQL database - #- database - - # Stats about the background writer process's activity - #- bgwriter - - # Stats about every PostgreSQL process - #- activity - - #enabled: true - #period: 10s - - # The host must be passed as PostgreSQL URL. Example: - # postgres://localhost:5432?sslmode=disable - # The available parameters are documented here: - # https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters - #hosts: ["postgres://localhost:5432"] - - # Username to use when connecting to PostgreSQL. Empty by default. - #username: user - - # Password to use when connecting to PostgreSQL. Empty by default. - #password: pass - - -#----------------------------- Prometheus Module ----------------------------- -#- module: prometheus - #metricsets: ["stats"] - #enabled: true - #period: 10s - #hosts: ["localhost:9090"] - #metrics_path: /metrics - #namespace: example - -#-------------------------------- Redis Module ------------------------------- -#- module: redis - #metricsets: ["info", "keyspace"] - #enabled: true - #period: 10s - - # Redis hosts - #hosts: ["127.0.0.1:6379"] - - # Timeout after which time a metricset should return an error - # Timeout is by default defined as period, as a fetch of a metricset - # should never take longer then period, as otherwise calls can pile up. - #timeout: 1s - - # Optional fields to be added to each event - #fields: - # datacenter: west - - # Network type to be used for redis connection. Default: tcp - #network: tcp - - # Max number of concurrent connections. Default: 10 - #maxconn: 10 - - # Filters can be used to reduce the number of fields sent. - #filters: - # - include_fields: - # fields: ["stats"] - - # Redis AUTH password. Empty by default. - #password: foobared - -#------------------------------ ZooKeeper Module ----------------------------- -#- module: zookeeper - #metricsets: ["mntr"] - #enabled: true - #period: 10s - #hosts: ["localhost:2181"] - - diff --git a/metricbeat/_meta/beat.yml b/metricbeat/_meta/beat.yml deleted file mode 100644 index 6ab098828ce..00000000000 --- a/metricbeat/_meta/beat.yml +++ /dev/null @@ -1,49 +0,0 @@ -###################### Metricbeat Configuration Example ####################### - -# This file is an example configuration file highlighting only the most common -# options. The metricbeat.full.yml file from the same directory contains all the -# supported options with more comments. You can use it as a reference. -# -# You can find the full configuration reference here: -# https://www.elastic.co/guide/en/beats/metricbeat/index.html - -#========================== Modules configuration ============================ -metricbeat.modules: - -#------------------------------- System Module ------------------------------- -- module: system - metricsets: - # CPU stats - - cpu - - # System Load stats - - load - - # Per CPU core stats - #- core - - # IO stats - #- diskio - - # Per filesystem stats - - filesystem - - # File system summary stats - - fsstat - - # Memory stats - - memory - - # Network stats - - network - - # Per process stats - - process - - # Sockets (linux only) - #- socket - enabled: true - period: 10s - processes: ['.*'] - - From a7eb97b2dffc5fbe350ed1e716715036e241c922 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Tue, 31 Jan 2017 10:49:39 +0100 Subject: [PATCH 34/78] Add prombeat link (#3482) --- libbeat/docs/communitybeats.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/libbeat/docs/communitybeats.asciidoc b/libbeat/docs/communitybeats.asciidoc index 83f86f117c4..ee6cc310333 100644 --- a/libbeat/docs/communitybeats.asciidoc +++ b/libbeat/docs/communitybeats.asciidoc @@ -42,6 +42,7 @@ managers. https://github.com/kozlice/phpfpmbeat[phpfpmbeat]:: Reads status from PHP-FPM. https://github.com/joshuar/pingbeat[pingbeat]:: Sends ICMP pings to a list of targets and stores the round trip time (RTT) in Elasticsearch. +https://github.com/carlpett/prombeat[prombeat]:: Index https://prometheus.io[Prometheus] metrics https://github.com/voigt/redditbeat[redditbeat]:: Collects new Reddit Submissions of one or multiple Subreddits. https://github.com/chrsblck/redisbeat[redisbeat]:: Used for Redis monitoring. https://github.com/consulthys/retsbeat[retsbeat]:: Collects counts of http://www.reso.org[RETS] resource/class records from https://en.wikipedia.org/wiki/Multiple_listing_service[Multiple Listing Service] (MLS) servers. From eab9a1e9ecb6f1c78d8bbcf72c158532b121b814 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 31 Jan 2017 13:01:36 +0100 Subject: [PATCH 35/78] Cleanup CEPH module (#3455) * Add system tests to check for doc fields * Fix docs / fields which do not correspond * Use shared client --- CHANGELOG.asciidoc | 2 ++ metricbeat/module/ceph/health/data.go | 7 ++--- metricbeat/module/ceph/health/health.go | 25 +++++----------- metricbeat/module/ceph/health/health_test.go | 4 +-- metricbeat/tests/system/test_ceph.py | 31 ++++++++++++++++++++ 5 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 metricbeat/tests/system/test_ceph.py diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 678caecde7c..5f04ca4cffa 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -89,6 +89,8 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add dynamic configuration reloading for modules. {pull}3281[3281] - Add docker health metricset {pull}3357[3357] - System module uses new matchers for white-listing processes. {pull}3469[3469] +- Add CEPH module with health metricset. {pull}3311[3311] +- Add php_fpm module with pool metricset. {pull}3415[3415] *Packetbeat* diff --git a/metricbeat/module/ceph/health/data.go b/metricbeat/module/ceph/health/data.go index e5b60bb1616..646ea1e1a67 100644 --- a/metricbeat/module/ceph/health/data.go +++ b/metricbeat/module/ceph/health/data.go @@ -2,7 +2,6 @@ package health import ( "encoding/json" - "io" "time" "github.com/elastic/beats/libbeat/common" @@ -69,10 +68,10 @@ type HealthRequest struct { Output Output `json:"output"` } -func eventsMapping(body io.Reader) []common.MapStr { +func eventsMapping(content []byte) []common.MapStr { var d HealthRequest - err := json.NewDecoder(body).Decode(&d) + err := json.Unmarshal(content, &d) if err != nil { logp.Err("Error: ", err) } @@ -81,7 +80,7 @@ func eventsMapping(body io.Reader) []common.MapStr { event := common.MapStr{ "cluster": common.MapStr{ - "overall_stats": d.Output.OverallStatus, + "overall_status": d.Output.OverallStatus, "timechecks": common.MapStr{ "epoch": d.Output.Timechecks.Epoch, "round": common.MapStr{ diff --git a/metricbeat/module/ceph/health/health.go b/metricbeat/module/ceph/health/health.go index f5e4aba8de6..ed16564e71c 100644 --- a/metricbeat/module/ceph/health/health.go +++ b/metricbeat/module/ceph/health/health.go @@ -1,11 +1,9 @@ package health import ( - "fmt" - "net/http" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" ) @@ -30,33 +28,24 @@ func init() { type MetricSet struct { mb.BaseMetricSet - client *http.Client + *helper.HTTP } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { logp.Warn("EXPERIMENTAL: The ceph health metricset is experimental") return &MetricSet{ - BaseMetricSet: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + base, + helper.NewHTTP(base), }, nil } func (m *MetricSet) Fetch() ([]common.MapStr, error) { - req, err := http.NewRequest("GET", m.HostData().SanitizedURI, nil) - - req.Header.Set("Accept", "application/json") - - resp, err := m.client.Do(req) + content, err := m.HTTP.FetchContent() if err != nil { - return nil, fmt.Errorf("error making http request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, resp.Status) + return nil, err } - return eventsMapping(resp.Body), nil + return eventsMapping(content), nil } diff --git a/metricbeat/module/ceph/health/health_test.go b/metricbeat/module/ceph/health/health_test.go index a3ea8c77608..c1058d62bf7 100644 --- a/metricbeat/module/ceph/health/health_test.go +++ b/metricbeat/module/ceph/health/health_test.go @@ -19,7 +19,7 @@ func TestFetchEventContents(t *testing.T) { response, err := ioutil.ReadFile(absPath + "/sample_response.json") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - w.Header().Set("Content-Type", "appication/json;") + w.Header().Set("Content-Type", "application/json;") w.Write([]byte(response)) })) defer server.Close() @@ -40,7 +40,7 @@ func TestFetchEventContents(t *testing.T) { t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) cluster := event["cluster"].(common.MapStr) - assert.EqualValues(t, "HEALTH_OK", cluster["overall_stats"]) + assert.EqualValues(t, "HEALTH_OK", cluster["overall_status"]) timechecks := cluster["timechecks"].(common.MapStr) assert.EqualValues(t, 3, timechecks["epoch"]) diff --git a/metricbeat/tests/system/test_ceph.py b/metricbeat/tests/system/test_ceph.py new file mode 100644 index 00000000000..34a9229695d --- /dev/null +++ b/metricbeat/tests/system/test_ceph.py @@ -0,0 +1,31 @@ +import os +import metricbeat +import unittest + + +class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_health(self): + """ + ceph health metricset test + """ + self.render_config_template(modules=[{ + "name": "ceph", + "metricsets": ["health"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + + def get_hosts(self): + return [os.getenv('CEPH_HOST', 'localhost') + ':' + + os.getenv('CEPH_PORT', '5000')] From ba01f41fa95e5cf48a5891cec9314bc5cd7ad824 Mon Sep 17 00:00:00 2001 From: markwalkom Date: Wed, 1 Feb 2017 00:47:05 +1100 Subject: [PATCH 36/78] Update outputconfig.asciidoc (#3476) Clarified how we handle things when multiple hosts are listed. --- libbeat/docs/outputconfig.asciidoc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libbeat/docs/outputconfig.asciidoc b/libbeat/docs/outputconfig.asciidoc index 4d8883ac188..713b462afc3 100644 --- a/libbeat/docs/outputconfig.asciidoc +++ b/libbeat/docs/outputconfig.asciidoc @@ -430,9 +430,12 @@ The default value is true. [[hosts]] ===== hosts -The list of known Logstash servers to connect to. All entries in this list can -contain a port number. If no port number is given, the value specified for <> -is used as the default port number. +The list of known Logstash servers to connect to. If load balancing is disabled, but +mutliple hosts are configured, one host is selected randomly (there is no precedence). +If one host becomes unreachable, another one is selected randomly. + +All entries in this list can contain a port number. If no port number is given, the +value specified for <> is used as the default port number. ===== compression_level From f7aa7b6b7f0f42708d253dc1df5807ffb8da53df Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 31 Jan 2017 16:22:11 +0100 Subject: [PATCH 37/78] Apply autopep8 to all python files (#3498) * Apply autopep8 to all python files Run the following command: ``` find . -name *.py -exec autopep8 --in-place --max-line-length 120 {} \; ``` * Convert print statements to all use brackets --- dev-tools/aggregate_coverage.py | 3 +- dev-tools/generate_notice.py | 1 - filebeat/scripts/create_fileset.py | 4 +- filebeat/tests/load/load.py | 6 +- filebeat/tests/open-file-handlers/log_file.py | 2 +- .../tests/open-file-handlers/log_stdout.py | 2 +- filebeat/tests/system/test_crawler.py | 6 +- filebeat/tests/system/test_fields.py | 2 +- filebeat/tests/system/test_harvester.py | 17 +- filebeat/tests/system/test_json.py | 11 +- filebeat/tests/system/test_load.py | 31 ++-- filebeat/tests/system/test_migration.py | 4 +- filebeat/tests/system/test_modules.py | 1 + filebeat/tests/system/test_multiline.py | 16 +- filebeat/tests/system/test_processors.py | 1 + filebeat/tests/system/test_prospector.py | 81 +++++----- filebeat/tests/system/test_publisher.py | 3 +- filebeat/tests/system/test_registrar.py | 61 +++---- filebeat/tests/system/test_reload.py | 1 + filebeat/tests/system/test_shutdown.py | 5 +- generate/beat.py | 1 - .../beat/{beat}/tests/system/test_base.py | 4 +- generate/beat/{beat}/tests/system/{beat}.py | 1 + generate/helper.py | 10 +- generate/metricbeat.py | 1 - heartbeat/tests/system/heartbeat.py | 1 + heartbeat/tests/system/test_base.py | 4 +- libbeat/scripts/create_packer.py | 15 +- libbeat/scripts/generate_fields_docs.py | 3 +- libbeat/scripts/generate_index_pattern.py | 13 +- libbeat/scripts/generate_makefile_doc.py | 34 ++-- .../scripts/migrate_beat_config_1_x_to_5_0.py | 3 +- libbeat/tests/system/beat/beat.py | 11 +- libbeat/tests/system/test_base.py | 1 + libbeat/tests/system/test_dashboard.py | 22 +-- metricbeat/scripts/config_collector.py | 3 +- metricbeat/scripts/create_metricset.py | 4 +- metricbeat/scripts/docs_collector.py | 8 +- metricbeat/scripts/fields_collector.py | 6 +- metricbeat/scripts/generate_imports.py | 3 +- metricbeat/tests/system/metricbeat.py | 2 + metricbeat/tests/system/test_apache.py | 4 +- metricbeat/tests/system/test_base.py | 1 + metricbeat/tests/system/test_config.py | 13 +- metricbeat/tests/system/test_docker.py | 1 + metricbeat/tests/system/test_haproxy.py | 5 +- metricbeat/tests/system/test_kafka.py | 3 +- metricbeat/tests/system/test_mysql.py | 3 +- metricbeat/tests/system/test_phpfpm.py | 2 + metricbeat/tests/system/test_processors.py | 18 +-- metricbeat/tests/system/test_prometheus.py | 2 + metricbeat/tests/system/test_redis.py | 4 +- metricbeat/tests/system/test_reload.py | 5 +- metricbeat/tests/system/test_system.py | 13 +- metricbeat/tests/system/test_zookeeper.py | 5 +- packetbeat/scripts/create_tcp_protocol.py | 8 +- packetbeat/scripts/generate_imports.py | 2 +- .../gen/memcache/tcp_multi_store_load.py | 6 +- .../gen/memcache/tcp_single_load_store.py | 2 +- .../system/gen/memcache/udp_multi_store.py | 6 +- .../system/gen/memcache/udp_single_store.py | 2 +- packetbeat/tests/system/packetbeat.py | 6 +- .../tests/system/test_0001_mysql_spaces.py | 1 + packetbeat/tests/system/test_0006_wsgi.py | 2 +- packetbeat/tests/system/test_0009_pgsql.py | 1 + .../system/test_0009_pgsql_extended_query.py | 2 +- .../tests/system/test_0012_http_basicauth.py | 4 +- packetbeat/tests/system/test_0015_udpjson.py | 1 + .../system/test_0017_mysql_long_result.py | 2 +- .../system/test_0018_pgsql_long_result.py | 4 +- .../tests/system/test_0023_http_params.py | 4 +- .../tests/system/test_0025_mongodb_basic.py | 2 +- packetbeat/tests/system/test_0029_http_gap.py | 2 +- .../tests/system/test_0030_mysql_gap.py | 2 +- packetbeat/tests/system/test_0031_vlans.py | 1 + packetbeat/tests/system/test_0032_dns.py | 1 + .../test_0040_memcache_tcp_bin_basic.py | 1 + .../test_0040_memcache_tcp_text_basic.py | 1 + .../test_0041_memcache_udp_bin_basic.py | 1 + .../test_0041_memcache_udp_text_basic.py | 1 + packetbeat/tests/system/test_0060_flows.py | 1 + packetbeat/tests/system/test_0061_nfs.py | 1 + .../tests/system/test_0062_cassandra.py | 104 +++++------- .../tests/system/test_0063_http_body.py | 4 +- .../tsg/gopacket/layers/test_creator.py | 152 +++++++++--------- winlogbeat/tests/system/test_config.py | 1 + winlogbeat/tests/system/test_eventlogging.py | 1 + winlogbeat/tests/system/test_wineventlog.py | 10 +- winlogbeat/tests/system/winlogbeat.py | 2 + 89 files changed, 408 insertions(+), 420 deletions(-) diff --git a/dev-tools/aggregate_coverage.py b/dev-tools/aggregate_coverage.py index 434a5e09eaf..d442069a1d5 100644 --- a/dev-tools/aggregate_coverage.py +++ b/dev-tools/aggregate_coverage.py @@ -8,6 +8,7 @@ import argparse import fnmatch + def main(arguments): parser = argparse.ArgumentParser(description=__doc__, @@ -34,7 +35,7 @@ def main(arguments): if not line.startswith('mode:') and "vendor" not in line: (position, stmt, count) = line.split(" ") stmt = int(stmt) - count = int (count) + count = int(count) prev_count = 0 if lines.has_key(position): (_, prev_stmt, prev_count) = lines[position] diff --git a/dev-tools/generate_notice.py b/dev-tools/generate_notice.py index bb309806827..3bcaec21fc7 100644 --- a/dev-tools/generate_notice.py +++ b/dev-tools/generate_notice.py @@ -49,7 +49,6 @@ def add_licenses(f, licenses): f.write(read_file(notice_file)) - def create_notice(filename, beat, copyright, licenses): now = datetime.datetime.now() diff --git a/filebeat/scripts/create_fileset.py b/filebeat/scripts/create_fileset.py index 31c9c24c70c..eab3e5c6f4d 100644 --- a/filebeat/scripts/create_fileset.py +++ b/filebeat/scripts/create_fileset.py @@ -84,11 +84,11 @@ def load_file(file, module, fileset): if args.path is None: args.path = './' - print "Set default path for beat path: " + args.path + print("Set default path for beat path: " + args.path) if args.es_beats is None: args.es_beats = '../' - print "Set default path for es_beats path: " + args.es_beats + print("Set default path for es_beats path: " + args.es_beats) if args.module is None or args.module == '': args.module = raw_input("Module name: ") diff --git a/filebeat/tests/load/load.py b/filebeat/tests/load/load.py index 77b3c701b4b..5e0fdd8979b 100644 --- a/filebeat/tests/load/load.py +++ b/filebeat/tests/load/load.py @@ -16,12 +16,12 @@ if not os.path.exists("logs"): os.mkdir("logs") -maxSize = 0.1 * 1000 * 1000 # 1MB +maxSize = 0.1 * 1000 * 1000 # 1MB rotatedFiles = 50 logsPerSecond = 10000 handler = logging.handlers.RotatingFileHandler( - LOG_FILENAME, maxBytes=maxSize, backupCount=rotatedFiles) + LOG_FILENAME, maxBytes=maxSize, backupCount=rotatedFiles) my_logger.addHandler(handler) count = 1 @@ -35,4 +35,4 @@ log_message = timestamp + " " + str(count) + " " + str(uuid.uuid4()) + " " + randomString my_logger.debug(log_message) count = count + 1 - time.sleep (sleepTime) + time.sleep(sleepTime) diff --git a/filebeat/tests/open-file-handlers/log_file.py b/filebeat/tests/open-file-handlers/log_file.py index a49b82a6e47..6299449a4cf 100644 --- a/filebeat/tests/open-file-handlers/log_file.py +++ b/filebeat/tests/open-file-handlers/log_file.py @@ -20,7 +20,7 @@ # Start logging and rotating i = 0 while True: -#for i in range(0, 10000): + # for i in range(0, 10000): time.sleep(random.uniform(0, 0.1)) i = i + 1 # Tries to cause some more heavy peaks diff --git a/filebeat/tests/open-file-handlers/log_stdout.py b/filebeat/tests/open-file-handlers/log_stdout.py index e72d0f11a30..dc1108f0baf 100644 --- a/filebeat/tests/open-file-handlers/log_stdout.py +++ b/filebeat/tests/open-file-handlers/log_stdout.py @@ -2,7 +2,7 @@ import sys for x in range(0, 100): - print x + print(x) time.sleep(1) sys.stdout.flush() diff --git a/filebeat/tests/system/test_crawler.py b/filebeat/tests/system/test_crawler.py index ee989355969..65791b37c6b 100644 --- a/filebeat/tests/system/test_crawler.py +++ b/filebeat/tests/system/test_crawler.py @@ -199,7 +199,7 @@ def test_file_renaming(self): # expecting 6 more events self.wait_until( - lambda: self.output_has(lines=iterations1+iterations2), max_timeout=10) + lambda: self.output_has(lines=iterations1 + iterations2), max_timeout=10) filebeat.check_kill_and_wait() @@ -360,7 +360,7 @@ def test_new_line_on_existing_file(self): f.write("hello world 2\n") self.wait_until( - lambda: self.output_has(lines=1+2), max_timeout=10) + lambda: self.output_has(lines=1 + 2), max_timeout=10) filebeat.check_kill_and_wait() @@ -531,7 +531,7 @@ def test_utf8(self): f.flush() self.wait_until( - lambda: self.output_has(lines=1+2), max_timeout=10) + lambda: self.output_has(lines=1 + 2), max_timeout=10) filebeat.check_kill_and_wait() diff --git a/filebeat/tests/system/test_fields.py b/filebeat/tests/system/test_fields.py index 632cb4aa384..09a8a3ac69d 100644 --- a/filebeat/tests/system/test_fields.py +++ b/filebeat/tests/system/test_fields.py @@ -54,7 +54,7 @@ def test_custom_fields_under_root(self): output = self.read_output() doc = output[0] - print doc + print(doc) assert doc["hello"] == "world" assert doc["type"] == "log2" assert doc["timestamp"] == 2 diff --git a/filebeat/tests/system/test_harvester.py b/filebeat/tests/system/test_harvester.py index 4c6fc52c622..1cdc5be9228 100644 --- a/filebeat/tests/system/test_harvester.py +++ b/filebeat/tests/system/test_harvester.py @@ -65,7 +65,6 @@ def test_close_renamed(self): # one entry for the new and one for the old should exist assert len(data) == 2 - def test_close_removed(self): """ Checks that a file is closed if removed @@ -115,7 +114,6 @@ def test_close_removed(self): # Make sure the state for the file was persisted assert len(data) == 1 - def test_close_eof(self): """ Checks that a file is closed if eof is reached @@ -143,7 +141,6 @@ def test_close_eof(self): self.wait_until( lambda: self.output_has(lines=iterations1), max_timeout=10) - # Wait until error shows up on windows self.wait_until( lambda: self.log_contains( @@ -157,7 +154,6 @@ def test_close_eof(self): # Make sure the state for the file was persisted assert len(data) == 1 - def test_empty_line(self): """ Checks that no empty events are sent for an empty line but state is still updated @@ -207,7 +203,6 @@ def test_empty_line(self): # Make sure the state for the file was persisted assert len(data) == 1 - def test_empty_lines_only(self): """ Checks that no empty events are sent for a file with only empty lines @@ -326,7 +321,6 @@ def test_truncated_file_open(self): filebeat.check_kill_and_wait() - def test_truncated_file_closed(self): """ Checks if it is correctly detected if a closed file is truncated @@ -443,7 +437,6 @@ def test_bom_utf8(self): filebeat.check_kill_and_wait() def test_boms(self): - """ Test bom log files if bom is removed properly """ @@ -525,7 +518,6 @@ def test_ignore_symlink(self): filebeat.check_kill_and_wait() - def test_symlinks_enabled(self): """ Test if symlinks are harvested @@ -558,7 +550,6 @@ def test_symlinks_enabled(self): filebeat.check_kill_and_wait() - def test_symlink_rotated(self): """ Test what happens if symlink removed and points to a new file @@ -622,7 +613,6 @@ def test_symlink_rotated(self): data = self.get_registry() assert len(data) == 2 - def test_symlink_removed(self): """ Tests that if a symlink to a file is removed, further data is read which is added to the original file @@ -770,14 +760,13 @@ def test_truncate(self): data = self.get_registry() assert len(data) == 1 - def test_decode_error(self): """ Tests that in case of a decoding error it is handled gracefully """ self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/*", - encoding="GBK", # Set invalid encoding for entry below which is actually uft-8 + encoding="GBK", # Set invalid encoding for entry below which is actually uft-8 ) os.mkdir(self.working_dir + "/log/") @@ -810,7 +799,3 @@ def test_decode_error(self): output = self.read_output_json() assert output[2]["message"] == "hello world2" - - - - diff --git a/filebeat/tests/system/test_json.py b/filebeat/tests/system/test_json.py index c708292888a..eb70cf61a02 100644 --- a/filebeat/tests/system/test_json.py +++ b/filebeat/tests/system/test_json.py @@ -7,6 +7,7 @@ class Test(BaseTest): + def test_docker_logs(self): """ Should be able to interpret docker logs. @@ -179,7 +180,7 @@ def test_timestamp_in_message(self): message_key="msg", keys_under_root=True, overwrite_keys=True - ), + ), ) os.mkdir(self.working_dir + "/log/") self.copy_files(["logs/json_timestamp.log"], @@ -222,7 +223,7 @@ def test_type_in_message(self): message_key="msg", keys_under_root=True, overwrite_keys=True - ), + ), ) os.mkdir(self.working_dir + "/log/") self.copy_files(["logs/json_type.log"], @@ -262,7 +263,7 @@ def test_with_generic_filtering(self): keys_under_root=True, overwrite_keys=True, add_error_key=True - ), + ), processors=[{ "drop_fields": { "fields": ["headers.request-id"], @@ -306,7 +307,7 @@ def test_with_generic_filtering_remove_headers(self): keys_under_root=True, overwrite_keys=True, add_error_key=True - ), + ), processors=[{ "drop_fields": { "fields": ["headers", "res"], @@ -346,7 +347,7 @@ def test_integer_condition(self): path=os.path.abspath(self.working_dir) + "/log/*", json=dict( keys_under_root=True, - ), + ), processors=[{ "drop_event": { "when": "equals.status: 200", diff --git a/filebeat/tests/system/test_load.py b/filebeat/tests/system/test_load.py index c8726f45ccd..6b027001972 100644 --- a/filebeat/tests/system/test_load.py +++ b/filebeat/tests/system/test_load.py @@ -16,6 +16,7 @@ class Test(BaseTest): + def test_no_missing_events(self): """ Test that filebeat does not loose any events under heavy file rotation and load @@ -44,7 +45,7 @@ def test_no_missing_events(self): self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/*", - rotate_every_kb=(total_lines * (line_length +1)), # With filepath, each line can be up to 1KB is assumed + rotate_every_kb=(total_lines * (line_length + 1)), # With filepath, each line can be up to 1KB is assumed clean_removed="false", ) @@ -79,15 +80,15 @@ def test_no_missing_events(self): ### This lines can be uncomemnted for debugging ### # Prints out the missing entries - #for i in range(total_lines): + # for i in range(total_lines): # if i not in entry_list: # print i # Stats about the files read #unique_entries = len(set(entry_list)) - #print "Total lines: " + str(total_lines) - #print "Total unique entries: " + str(unique_entries) - #print "Total entries: " + str(len(entry_list)) - #print "Registry entries: " + str(len(data)) + # print "Total lines: " + str(total_lines) + # print "Total unique entries: " + str(unique_entries) + # print "Total entries: " + str(len(entry_list)) + # print "Registry entries: " + str(len(data)) # Check that file exist data = self.get_registry() @@ -102,7 +103,6 @@ def test_no_missing_events(self): assert len(set(entry_list)) == total_lines assert len(entry_list) == total_lines - @unittest.skipUnless(LOAD_TESTS, "load test") @attr('load') def test_large_number_of_files(self): @@ -116,22 +116,21 @@ def test_large_number_of_files(self): # Create content for each file content = "" for n in range(lines_per_file): - content += "Line " + str(n+1) + "\n" + content += "Line " + str(n + 1) + "\n" os.mkdir(self.working_dir + "/log/") testfile = self.working_dir + "/log/test" for n in range(number_of_files): - with open(testfile + "-" + str(n+1), 'w') as f: + with open(testfile + "-" + str(n + 1), 'w') as f: f.write(content) - self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/*", rotate_every_kb=number_of_files * lines_per_file * 12 * 2, scan_frequency="40s", - #close_inactive="5s", - #close_eof=True, + # close_inactive="5s", + # close_eof=True, ) filebeat = self.start_beat() @@ -146,7 +145,6 @@ def test_large_number_of_files(self): data = self.get_registry() assert len(data) == number_of_files - @unittest.skipUnless(LOAD_TESTS, "load test") @attr('load') def test_concurrent_harvesters(self): @@ -159,16 +157,15 @@ def test_concurrent_harvesters(self): # Create content for each file content = "" for n in range(lines_per_file): - content += "Line " + str(n+1) + "\n" + content += "Line " + str(n + 1) + "\n" os.mkdir(self.working_dir + "/log/") testfile = self.working_dir + "/log/test" for n in range(number_of_files): - with open(testfile + "-" + str(n+1), 'w') as f: + with open(testfile + "-" + str(n + 1), 'w') as f: f.write(content) - self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/*", rotate_every_kb=number_of_files * lines_per_file * 12 * 2, @@ -177,7 +174,7 @@ def test_concurrent_harvesters(self): total_lines = number_of_files * lines_per_file - print total_lines + print(total_lines) # wait until all lines are read self.wait_until( lambda: self.output_has(lines=total_lines), diff --git a/filebeat/tests/system/test_migration.py b/filebeat/tests/system/test_migration.py index 62fe99c0198..d26645c5fac 100644 --- a/filebeat/tests/system/test_migration.py +++ b/filebeat/tests/system/test_migration.py @@ -48,7 +48,8 @@ def test_migration_non_windows(self): assert self.get_registry_entry_by_path("logs/log2.log")["offset"] == 6 # Compare first entry - oldJson = json.loads('{"source":"logs/hello.log","offset":4,"FileStateOS":{"inode":30178938,"device":16777220}}') + oldJson = json.loads( + '{"source":"logs/hello.log","offset":4,"FileStateOS":{"inode":30178938,"device":16777220}}') newJson = self.get_registry_entry_by_path("logs/hello.log") del newJson["timestamp"] del newJson["ttl"] @@ -153,7 +154,6 @@ def test_migration_continue_reading(self): with open(registry_file, 'w') as f: json.dump(old_registry, f) - self.render_config_template( path=os.path.abspath(self.working_dir) + "/log/*", output_file_filename="filebeat_2", diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 2f439a6418a..304742edf45 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -10,6 +10,7 @@ class Test(BaseTest): + def init(self): self.elasticsearch_url = self.get_elasticsearch_url() print("Using elasticsearch: {}".format(self.elasticsearch_url)) diff --git a/filebeat/tests/system/test_multiline.py b/filebeat/tests/system/test_multiline.py index e0c5ed27bff..74d9fc5d3b4 100644 --- a/filebeat/tests/system/test_multiline.py +++ b/filebeat/tests/system/test_multiline.py @@ -78,11 +78,11 @@ def test_rabbitmq_multiline_log(self): Special about this log file is that it has empty new lines """ self.render_config_template( - path=os.path.abspath(self.working_dir) + "/log/*", - multiline=True, - pattern="^=[A-Z]+", - match="after", - negate="true", + path=os.path.abspath(self.working_dir) + "/log/*", + multiline=True, + pattern="^=[A-Z]+", + match="after", + negate="true", ) logentry = """=ERROR REPORT==== 3-Feb-2016::03:10:32 === @@ -106,8 +106,8 @@ def test_rabbitmq_multiline_log(self): # wait for the "Skipping file" log message self.wait_until( - lambda: self.output_has(lines=3), - max_timeout=10) + lambda: self.output_has(lines=3), + max_timeout=10) proc.check_kill_and_wait() @@ -293,7 +293,6 @@ def test_close_timeout_with_multiline(self): assert 3 == len(output) def test_consecutive_newline(self): - """ Test if consecutive multilines have an affect on multiline """ @@ -318,7 +317,6 @@ def test_consecutive_newline(self): SetAdCodeMiddleware.default_ad_code path /health_check SetAdCodeMiddleware.default_ad_code route """ - os.mkdir(self.working_dir + "/log/") testfile = self.working_dir + "/log/test.log" diff --git a/filebeat/tests/system/test_processors.py b/filebeat/tests/system/test_processors.py index 1798025a2e9..47fb701f09c 100644 --- a/filebeat/tests/system/test_processors.py +++ b/filebeat/tests/system/test_processors.py @@ -7,6 +7,7 @@ class Test(BaseTest): + def test_dropfields(self): """ Check drop_fields filtering action diff --git a/filebeat/tests/system/test_prospector.py b/filebeat/tests/system/test_prospector.py index 330ebf2528f..1184673dad0 100644 --- a/filebeat/tests/system/test_prospector.py +++ b/filebeat/tests/system/test_prospector.py @@ -311,7 +311,6 @@ def test_no_paths_defined(self): filebeat.check_wait(exit_code=1) - def test_files_added_late(self): """ Tests that prospectors stay running even though no harvesters are started yet @@ -361,9 +360,9 @@ def test_close_inactive(self): # wait for first "Start next scan" log message self.wait_until( - lambda: self.log_contains( - "Start next scan"), - max_timeout=10) + lambda: self.log_contains( + "Start next scan"), + max_timeout=10) lines = 0 @@ -374,14 +373,14 @@ def test_close_inactive(self): # wait for log to be read self.wait_until( - lambda: self.output_has(lines=lines), - max_timeout=15) + lambda: self.output_has(lines=lines), + max_timeout=15) # wait for file to be closed due to close_inactive self.wait_until( - lambda: self.log_contains( - "Closing file: {}\n".format(os.path.abspath(testfile))), - max_timeout=10) + lambda: self.log_contains( + "Closing file: {}\n".format(os.path.abspath(testfile))), + max_timeout=10) # write second line lines += 1 @@ -389,9 +388,9 @@ def test_close_inactive(self): file.write("Line {}\n".format(lines)) self.wait_until( - # allow for events to be sent multiple times due to log rotation - lambda: self.output_count(lambda x: x >= lines), - max_timeout=5) + # allow for events to be sent multiple times due to log rotation + lambda: self.output_count(lambda x: x >= lines), + max_timeout=5) filebeat.check_kill_and_wait() @@ -413,9 +412,9 @@ def test_close_inactive_file_removal(self): # wait for first "Start next scan" log message self.wait_until( - lambda: self.log_contains( - "Start next scan"), - max_timeout=10) + lambda: self.log_contains( + "Start next scan"), + max_timeout=10) lines = 0 @@ -426,20 +425,19 @@ def test_close_inactive_file_removal(self): # wait for log to be read self.wait_until( - lambda: self.output_has(lines=lines), - max_timeout=15) + lambda: self.output_has(lines=lines), + max_timeout=15) os.remove(testfile) # wait for file to be closed due to close_inactive self.wait_until( - lambda: self.log_contains( - "Closing file: {}\n".format(os.path.abspath(testfile))), - max_timeout=10) + lambda: self.log_contains( + "Closing file: {}\n".format(os.path.abspath(testfile))), + max_timeout=10) filebeat.check_kill_and_wait() - def test_close_inactive_file_rotation_and_removal(self): """ Test that close_inactive still applies also if the file to close was removed @@ -459,9 +457,9 @@ def test_close_inactive_file_rotation_and_removal(self): # wait for first "Start next scan" log message self.wait_until( - lambda: self.log_contains( - "Start next scan"), - max_timeout=10) + lambda: self.log_contains( + "Start next scan"), + max_timeout=10) lines = 0 @@ -472,22 +470,21 @@ def test_close_inactive_file_rotation_and_removal(self): # wait for log to be read self.wait_until( - lambda: self.output_has(lines=lines), - max_timeout=15) + lambda: self.output_has(lines=lines), + max_timeout=15) os.rename(testfile, renamed_file) os.remove(renamed_file) # wait for file to be closed due to close_inactive self.wait_until( - lambda: self.log_contains( - # Still checking for old file name as filename does not change in harvester - "Closing file: "), - max_timeout=10) + lambda: self.log_contains( + # Still checking for old file name as filename does not change in harvester + "Closing file: "), + max_timeout=10) filebeat.check_kill_and_wait() - def test_close_inactive_file_rotation_and_removal_while_new_file_created(self): """ Test that close_inactive still applies also if file was rotated, @@ -508,9 +505,9 @@ def test_close_inactive_file_rotation_and_removal_while_new_file_created(self): # wait for first "Start next scan" log message self.wait_until( - lambda: self.log_contains( - "Start next scan"), - max_timeout=10) + lambda: self.log_contains( + "Start next scan"), + max_timeout=10) lines = 0 @@ -521,8 +518,8 @@ def test_close_inactive_file_rotation_and_removal_while_new_file_created(self): # wait for log to be read self.wait_until( - lambda: self.output_has(lines=lines), - max_timeout=15) + lambda: self.output_has(lines=lines), + max_timeout=15) os.rename(testfile, renamed_file) @@ -533,17 +530,17 @@ def test_close_inactive_file_rotation_and_removal_while_new_file_created(self): # wait for log to be read self.wait_until( - lambda: self.output_has(lines=lines), - max_timeout=15) + lambda: self.output_has(lines=lines), + max_timeout=15) os.remove(renamed_file) # Wait until both files are closed self.wait_until( - lambda: self.log_contains_count( - # Checking if two files were closed - "Closing file: ") == 2, - max_timeout=10) + lambda: self.log_contains_count( + # Checking if two files were closed + "Closing file: ") == 2, + max_timeout=10) filebeat.check_kill_and_wait() diff --git a/filebeat/tests/system/test_publisher.py b/filebeat/tests/system/test_publisher.py index e93788c0171..679d4ee1be9 100644 --- a/filebeat/tests/system/test_publisher.py +++ b/filebeat/tests/system/test_publisher.py @@ -34,7 +34,7 @@ def test_registrar_file_content(self): iterations = 5 for n in range(0, iterations): - file.write("line " + str(n+1)) + file.write("line " + str(n + 1)) file.write("\n") file.close() @@ -57,4 +57,3 @@ def test_registrar_file_content(self): data = self.get_registry() assert len(data) == 1 assert self.output_has(lines=iterations) - diff --git a/filebeat/tests/system/test_registrar.py b/filebeat/tests/system/test_registrar.py index 7a57485435c..989fc483491 100644 --- a/filebeat/tests/system/test_registrar.py +++ b/filebeat/tests/system/test_registrar.py @@ -256,7 +256,8 @@ def test_rotating_file_inode(self): max_timeout=10) data = self.get_registry() - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] testfilerenamed1 = self.working_dir + "/log/input.1" os.rename(testfile, testfilerenamed1) @@ -278,8 +279,10 @@ def test_rotating_file_inode(self): data = self.get_registry() - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] - assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] # Rotate log file, create a new empty one and remove it afterwards testfilerenamed2 = self.working_dir + "/log/input.2" @@ -303,13 +306,15 @@ def test_rotating_file_inode(self): data = self.get_registry() # Compare file inodes and the one in the registry - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] - assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] - # Check that 3 files are part of the registrar file. The deleted file should never have been detected, but the rotated one should be in + # Check that 3 files are part of the registrar file. The deleted file + # should never have been detected, but the rotated one should be in assert len(data) == 3 - def test_restart_continue(self): """ Check that file readining continues after restart @@ -337,7 +342,8 @@ def test_restart_continue(self): # Wait a momemt to make sure registry is completely written time.sleep(1) - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] filebeat.check_kill_and_wait() @@ -364,7 +370,8 @@ def test_restart_continue(self): data = self.get_registry() # Compare file inodes and the one in the registry - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] # Check that 1 files are part of the registrar file. The deleted file should never have been detected assert len(data) == 1 @@ -375,7 +382,6 @@ def test_restart_continue(self): assert 1 == len(output) assert output[0]["message"] == "entry2" - def test_rotating_file_with_restart(self): """ Check that inodes are properly written during file rotation and restart @@ -405,7 +411,8 @@ def test_rotating_file_with_restart(self): time.sleep(1) data = self.get_registry() - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] testfilerenamed1 = self.working_dir + "/log/input.1" os.rename(testfile, testfilerenamed1) @@ -428,8 +435,10 @@ def test_rotating_file_with_restart(self): data = self.get_registry() - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] - assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] filebeat.check_kill_and_wait() @@ -465,10 +474,13 @@ def test_rotating_file_with_restart(self): data = self.get_registry() # Compare file inodes and the one in the registry - assert os.stat(testfile).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfile))["FileStateOS"]["inode"] - assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path(os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] + assert os.stat(testfile).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfile))["FileStateOS"]["inode"] + assert os.stat(testfilerenamed1).st_ino == self.get_registry_entry_by_path( + os.path.abspath(testfilerenamed1))["FileStateOS"]["inode"] - # Check that 3 files are part of the registrar file. The deleted file should never have been detected, but the rotated one should be in + # Check that 3 files are part of the registrar file. The deleted file + # should never have been detected, but the rotated one should be in assert len(data) == 3 def test_state_after_rotation(self): @@ -545,7 +557,6 @@ def test_state_after_rotation(self): assert self.get_registry_entry_by_path(os.path.abspath(testfile1))["offset"] == 9 assert self.get_registry_entry_by_path(os.path.abspath(testfile2))["offset"] == 8 - def test_state_after_rotation_ignore_older(self): """ Checks that the state is written correctly after rotation and ignore older @@ -557,7 +568,6 @@ def test_state_after_rotation_ignore_older(self): close_inactive="1s" ) - os.mkdir(self.working_dir + "/log/") testfile1 = self.working_dir + "/log/input" testfile2 = self.working_dir + "/log/input.1" @@ -570,7 +580,7 @@ def test_state_after_rotation_ignore_older(self): f.write("entry0\n") # Change modification time so file extends ignore_older - yesterday = time.time() - 3600*24 + yesterday = time.time() - 3600 * 24 os.utime(testfile2, (yesterday, yesterday)) filebeat = self.start_beat() @@ -697,7 +707,6 @@ def test_clean_inactive(self): else: assert data[0]["offset"] == 2 - def test_clean_removed(self): """ Checks that files which were removed, the state is removed @@ -977,7 +986,6 @@ def test_restart_state(self): filebeat.check_kill_and_wait() - def test_restart_state_reset(self): """ Test that ttl is set to -1 after restart and no prospector covering it @@ -1056,7 +1064,7 @@ def test_restart_state_reset_ttl(self): self.wait_until( lambda: self.log_contains("Registry file updated. 1 states written.", - logfile="filebeat.log"), max_timeout=10) + logfile="filebeat.log"), max_timeout=10) filebeat.check_kill_and_wait() @@ -1078,11 +1086,11 @@ def test_restart_state_reset_ttl(self): self.wait_until( lambda: self.log_contains("Flushing spooler because of timeout. Events flushed: ", - logfile="filebeat2.log"), max_timeout=10) + logfile="filebeat2.log"), max_timeout=10) self.wait_until( lambda: self.log_contains("Registry file updated", - logfile="filebeat2.log"), max_timeout=10) + logfile="filebeat2.log"), max_timeout=10) filebeat.check_kill_and_wait() @@ -1118,7 +1126,7 @@ def test_restart_state_reset_ttl_with_space(self): self.wait_until( lambda: self.log_contains("Registry file updated. 1 states written.", - logfile="filebeat.log"), max_timeout=10) + logfile="filebeat.log"), max_timeout=10) filebeat.check_kill_and_wait() @@ -1143,7 +1151,7 @@ def test_restart_state_reset_ttl_with_space(self): self.wait_until( lambda: self.log_contains("Registry file updated", - logfile="filebeat2.log"), max_timeout=10) + logfile="filebeat2.log"), max_timeout=10) filebeat.check_kill_and_wait() @@ -1152,7 +1160,6 @@ def test_restart_state_reset_ttl_with_space(self): assert len(data) == 1 assert data[0]["ttl"] == 40 * 1000 * 1000 * 1000 - def test_restart_state_reset_ttl_no_clean_inactive(self): """ Test that ttl is reset after restart if clean_inactive is disabled diff --git a/filebeat/tests/system/test_reload.py b/filebeat/tests/system/test_reload.py index 27bf73afaef..5df8131c3a4 100644 --- a/filebeat/tests/system/test_reload.py +++ b/filebeat/tests/system/test_reload.py @@ -13,6 +13,7 @@ scan_frequency: 1s """ + class Test(BaseTest): def test_reload(self): diff --git a/filebeat/tests/system/test_shutdown.py b/filebeat/tests/system/test_shutdown.py index 836e8fd6f97..3ad8dc63f0d 100644 --- a/filebeat/tests/system/test_shutdown.py +++ b/filebeat/tests/system/test_shutdown.py @@ -8,6 +8,7 @@ Tests that Filebeat shuts down cleanly. """ + class Test(BaseTest): def test_shutdown(self): @@ -21,7 +22,7 @@ def test_shutdown(self): path=os.path.abspath(self.working_dir) + "/log/*", ignore_older="1h" ) - for i in range(1,5): + for i in range(1, 5): proc = self.start_beat(logging_args=["-e", "-v"]) time.sleep(.5) proc.check_kill_and_wait() @@ -113,7 +114,7 @@ def test_once(self): iterations = 100 for n in range(0, iterations): - file.write("entry " + str(n+1)) + file.write("entry " + str(n + 1)) file.write("\n") file.close() diff --git a/generate/beat.py b/generate/beat.py index a7bffa1a4e8..7b75bfe6ad1 100644 --- a/generate/beat.py +++ b/generate/beat.py @@ -5,4 +5,3 @@ parser = helper.get_parser() args = parser.parse_args() helper.generate_beat("beat", args) - diff --git a/generate/beat/{beat}/tests/system/test_base.py b/generate/beat/{beat}/tests/system/test_base.py index c8501419124..44e4e0ec7ca 100644 --- a/generate/beat/{beat}/tests/system/test_base.py +++ b/generate/beat/{beat}/tests/system/test_base.py @@ -10,10 +10,10 @@ def test_base(self): Basic test with exiting {Beat} normally """ self.render_config_template( - path=os.path.abspath(self.working_dir) + "/log/*" + path=os.path.abspath(self.working_dir) + "/log/*" ) {beat}_proc = self.start_beat() - self.wait_until( lambda: self.log_contains("{beat} is running")) + self.wait_until(lambda: self.log_contains("{beat} is running")) exit_code = {beat}_proc.kill_and_wait() assert exit_code == 0 diff --git a/generate/beat/{beat}/tests/system/{beat}.py b/generate/beat/{beat}/tests/system/{beat}.py index 1e83d93370d..4f39dfba88a 100644 --- a/generate/beat/{beat}/tests/system/{beat}.py +++ b/generate/beat/{beat}/tests/system/{beat}.py @@ -2,6 +2,7 @@ sys.path.append('../../vendor/github.com/elastic/beats/libbeat/tests/system') from beat.beat import TestCase + class BaseTest(TestCase): @classmethod diff --git a/generate/helper.py b/generate/helper.py index 70b63729443..6ea5291cc74 100644 --- a/generate/helper.py +++ b/generate/helper.py @@ -9,6 +9,7 @@ beat_path = "" full_name = "" + def generate_beat(template_path, args): global project_name, github_name, beat, beat_path, full_name @@ -28,6 +29,7 @@ def generate_beat(template_path, args): read_input() process_file(template_path) + def read_input(): """Requests input form the command line for empty variables if needed. """ @@ -41,11 +43,13 @@ def read_input(): beat = project_name.lower() if beat_path == "": - beat_path = raw_input("Beat Path [github.com/" + github_name + "/" + beat + "]: ") or "github.com/" + github_name + "/" + beat + beat_path = raw_input("Beat Path [github.com/" + github_name + "/" + beat + + "]: ") or "github.com/" + github_name + "/" + beat if full_name == "": full_name = raw_input("Firstname Lastname: ") or "Firstname Lastname" + def process_file(template_path): # Load path information @@ -58,7 +62,7 @@ def process_file(template_path): full_path = root + "/" + file - ## load file + # load file content = "" with open(full_path) as f: content = f.read() @@ -84,6 +88,7 @@ def process_file(template_path): with open(write_file, 'w') as f: f.write(content) + def replace_variables(content): """Replace all template variables with the actual values """ @@ -105,4 +110,3 @@ def get_parser(): parser.add_argument("--full_name", help="Full name") return parser - diff --git a/generate/metricbeat.py b/generate/metricbeat.py index 022f33ad003..b58cbf337e4 100644 --- a/generate/metricbeat.py +++ b/generate/metricbeat.py @@ -5,4 +5,3 @@ parser = helper.get_parser() args = parser.parse_args() helper.generate_beat("metricbeat", args) - diff --git a/heartbeat/tests/system/heartbeat.py b/heartbeat/tests/system/heartbeat.py index 48e998120e1..6d175f9f52f 100644 --- a/heartbeat/tests/system/heartbeat.py +++ b/heartbeat/tests/system/heartbeat.py @@ -2,6 +2,7 @@ sys.path.append('../../vendor/github.com/elastic/beats/libbeat/tests/system') from beat.beat import TestCase + class BaseTest(TestCase): @classmethod diff --git a/heartbeat/tests/system/test_base.py b/heartbeat/tests/system/test_base.py index 75fd1fa3d64..b71650b9b64 100644 --- a/heartbeat/tests/system/test_base.py +++ b/heartbeat/tests/system/test_base.py @@ -10,10 +10,10 @@ def test_base(self): Basic test with exiting Heartbeat normally """ self.render_config_template( - path=os.path.abspath(self.working_dir) + "/log/*" + path=os.path.abspath(self.working_dir) + "/log/*" ) heartbeat_proc = self.start_beat() - self.wait_until( lambda: self.log_contains("heartbeat is running")) + self.wait_until(lambda: self.log_contains("heartbeat is running")) exit_code = heartbeat_proc.kill_and_wait() assert exit_code == 0 diff --git a/libbeat/scripts/create_packer.py b/libbeat/scripts/create_packer.py index d8509e557ef..b09a649635d 100644 --- a/libbeat/scripts/create_packer.py +++ b/libbeat/scripts/create_packer.py @@ -3,15 +3,16 @@ # Adds dev-tools/packer directory with the necessary files to a beat + def generate_packer(es_beats, abs_path, beat, beat_path, version): # create dev-tools/packer packer_path = abs_path + "/dev-tools/packer" - print packer_path + print(packer_path) if os.path.isdir(packer_path): - print "Dev tools already exists. Stopping..." + print("Dev tools already exists. Stopping...") return # create all directories needed @@ -23,17 +24,15 @@ def generate_packer(es_beats, abs_path, beat, beat_path, version): with open(packer_path + "/version.yml", "w") as f: f.write(content) - content = load_file(templates + "/Makefile", beat, beat_path, version) with open(packer_path + "/Makefile", "w") as f: f.write(content) - content = load_file(templates + "/config.yml", beat, beat_path, version) with open(packer_path + "/beats/" + beat + ".yml", "w") as f: f.write(content) - print "Packer directories created" + print("Packer directories created") def load_file(file, beat, beat_path, version): @@ -60,10 +59,10 @@ def load_file(file, beat, beat_path, version): abs_path = os.path.abspath("./") # Removes the gopath + /src/ from the directory name to fetch the path - beat_path = abs_path[len(gopath)+5:] + beat_path = abs_path[len(gopath) + 5:] - print beat_path - print abs_path + print(beat_path) + print(abs_path) es_beats = os.path.abspath(args.es_beats) generate_packer(es_beats, abs_path, args.beat, beat_path, args.version) diff --git a/libbeat/scripts/generate_fields_docs.py b/libbeat/scripts/generate_fields_docs.py index f01bace2f07..e3635296a15 100644 --- a/libbeat/scripts/generate_fields_docs.py +++ b/libbeat/scripts/generate_fields_docs.py @@ -2,6 +2,7 @@ import os import argparse + def document_fields(output, section, sections, path): if "anchor" in section: output.write("[[exported-fields-{}]]\n".format(section["anchor"])) @@ -74,7 +75,6 @@ def fields_to_asciidoc(input, output, beat): """.format(**dict)) - docs = yaml.load(input) # fields file is empty @@ -99,7 +99,6 @@ def fields_to_asciidoc(input, output, beat): if __name__ == "__main__": - parser = argparse.ArgumentParser( description="Generates the documentation for a Beat.") parser.add_argument("path", help="Path to the beat folder") diff --git a/libbeat/scripts/generate_index_pattern.py b/libbeat/scripts/generate_index_pattern.py index 4cb01d9c2d4..94c40a673ed 100644 --- a/libbeat/scripts/generate_index_pattern.py +++ b/libbeat/scripts/generate_index_pattern.py @@ -38,7 +38,8 @@ def field_to_json(desc, path, output, global unique_fields if path in unique_fields: - print("ERROR: Field {} is duplicated. Please delete it and try again. Fields already are {}".format(path, ", ".join(unique_fields))) + print("ERROR: Field {} is duplicated. Please delete it and try again. Fields already are {}".format( + path, ", ".join(unique_fields))) sys.exit(1) else: unique_fields.append(path) @@ -154,15 +155,17 @@ def get_index_pattern_name(index): # dump output to a json file fileName = get_index_pattern_name(args.index) target_dir = os.path.join(args.beat, "_meta", "kibana", "index-pattern") - target_file =os.path.join(target_dir, fileName + ".json") + target_file = os.path.join(target_dir, fileName + ".json") - try: os.makedirs(target_dir) + try: + os.makedirs(target_dir) except OSError as exception: - if exception.errno != errno.EEXIST: raise + if exception.errno != errno.EEXIST: + raise output = json.dumps(output, indent=2) with open(target_file, 'w') as f: f.write(output) - print ("The index pattern was created under {}".format(target_file)) + print("The index pattern was created under {}".format(target_file)) diff --git a/libbeat/scripts/generate_makefile_doc.py b/libbeat/scripts/generate_makefile_doc.py index 209fdfca7ad..c8c7aa30c08 100644 --- a/libbeat/scripts/generate_makefile_doc.py +++ b/libbeat/scripts/generate_makefile_doc.py @@ -27,8 +27,8 @@ # varname => BEAT_NAME # category => testing # doc => Runs the unit tests without coverage reports. -regexp_target_doc = re.compile(r'^((?P(-|_|\w)+)|(\${(?P(-|_|\w)+)}))\s*:.*\#\#+\s*@(?P(\w+))\s+(?P(.*))') - +regexp_target_doc = re.compile( + r'^((?P(-|_|\w)+)|(\${(?P(-|_|\w)+)}))\s*:.*\#\#+\s*@(?P(\w+))\s+(?P(.*))') # Parse a Makefile variable assignement: @@ -40,14 +40,15 @@ # category => packaging # doc => Software license of the application # -## Example 2: +# Example 2: # BEAT_NAME?=filebeat # name => BEAT_NAME # default => libbeat # category => None # doc => None # -regexp_var_help = re.compile(r'^(?P(\w)+)\s*(\?)?=\s*(?P([^\#]+))(\s+\#\#+\s*@(?P(\w+))(:)?\s+(?P(.*))|\s*$)') +regexp_var_help = re.compile( + r'^(?P(\w)+)\s*(\?)?=\s*(?P([^\#]+))(\s+\#\#+\s*@(?P(\w+))(:)?\s+(?P(.*))|\s*$)') # Parse a Makefile line according to the given regexp @@ -81,7 +82,7 @@ def parse_line(line, regexp, categories, categories_set): if category: category = category.replace("_", " ").capitalize() doc = matches.group("doc").rstrip('.').rstrip() - doc = doc[0].capitalize() + doc[1:] # Capitalize the first word + doc = doc[0].capitalize() + doc[1:] # Capitalize the first word if category not in categories_set: categories_set.append(category) @@ -113,31 +114,33 @@ def substitute_variable_targets(targets, variables): variable['variable'] = False # Display the help to stdout + + def print_help(categories, categories_set): column_size = max(len(rule["name"]) for category in categories_set for rule in categories[category]) for category in categories_set: - print ("\n{}:".format(category)) + print("\n{}:".format(category)) for rule in categories[category]: if "name" in rule: name = rule["name"] if "varname" in rule: name = rule["varname"] default = rule["default"] - print ("\t{target: <{fill}}\t{doc}.{default}".format( + print("\t{target: <{fill}}\t{doc}.{default}".format( target=rule["name"], fill=column_size, - doc=rule["doc"], - default=(" Default: {}".format(default) if default else ""))) + doc=rule["doc"], + default=(" Default: {}".format(default) if default else ""))) if __name__ == "__main__": parser = argparse.ArgumentParser( description="Generate documentation from a list of Makefile files") - parser.add_argument( "--variables", dest='variables', - action='store_true') + parser.add_argument("--variables", dest='variables', + action='store_true') - parser.add_argument( "files", nargs="+", type=argparse.FileType('r'), - help="list of Makefiles to analyze", - default=None) + parser.add_argument("files", nargs="+", type=argparse.FileType('r'), + help="list of Makefiles to analyze", + default=None) args = parser.parse_args() categories_targets = {} @@ -156,8 +159,7 @@ def print_help(categories, categories_set): substitute_variable_targets(categories_targets, variables) if not args.variables: - print ("Usage: make [target] [VARIABLE=value]") + print("Usage: make [target] [VARIABLE=value]") print_help(categories_targets, categories_targets_set) else: print_help(categories_vars, categories_vars_set) - diff --git a/libbeat/scripts/migrate_beat_config_1_x_to_5_0.py b/libbeat/scripts/migrate_beat_config_1_x_to_5_0.py index 65c98385298..b7d061a5fa8 100644 --- a/libbeat/scripts/migrate_beat_config_1_x_to_5_0.py +++ b/libbeat/scripts/migrate_beat_config_1_x_to_5_0.py @@ -110,7 +110,7 @@ def make_version_info(): v_start = get_old_tls_version(v_start, "1.0") v_end = get_old_tls_version(v_end, "1.2") - versions = (ssl_versions_new[i] for i in xrange(v_start, v_end+1)) + versions = (ssl_versions_new[i] for i in xrange(v_start, v_end + 1)) line = indent * ' ' + ('#' if commented_out else '') line += "supported_protocols:" @@ -305,6 +305,7 @@ def test_migrate_shipper(): test: """ + def test_migrate_tls_settings(): test = """ output: diff --git a/libbeat/tests/system/beat/beat.py b/libbeat/tests/system/beat/beat.py index 6e3379a6e8f..e1eb9f24ab6 100644 --- a/libbeat/tests/system/beat/beat.py +++ b/libbeat/tests/system/beat/beat.py @@ -16,6 +16,7 @@ INTEGRATION_TESTS = os.environ.get('INTEGRATION_TESTS', False) + class Proc(object): """ Slim wrapper on subprocess.Popen that redirects @@ -68,7 +69,8 @@ def wait(self): def check_wait(self, exit_code=0): actual_exit_code = self.wait() - assert actual_exit_code == exit_code, "Expected exit code to be %d, but it was %d" % (exit_code, actual_exit_code) + assert actual_exit_code == exit_code, "Expected exit code to be %d, but it was %d" % ( + exit_code, actual_exit_code) return actual_exit_code def kill_and_wait(self): @@ -96,6 +98,7 @@ def __del__(self): class TestCase(unittest.TestCase): + @classmethod def setUpClass(self): @@ -201,7 +204,7 @@ def read_output(self, jsons = [] with open(os.path.join(self.working_dir, output_file), "r") as f: for line in f: - if len(line) == 0 or line[len(line)-1] != "\n": + if len(line) == 0 or line[len(line) - 1] != "\n": # hit EOF break @@ -224,7 +227,7 @@ def read_output_json(self, output_file=None): jsons = [] with open(os.path.join(self.working_dir, output_file), "r") as f: for line in f: - if len(line) == 0 or line[len(line)-1] != "\n": + if len(line) == 0 or line[len(line) - 1] != "\n": # hit EOF break @@ -285,7 +288,7 @@ def get_log(self, logfile=None): logfile = self.beat_name + ".log" with open(os.path.join(self.working_dir, logfile), 'r') as f: - data=f.read() + data = f.read() return data diff --git a/libbeat/tests/system/test_base.py b/libbeat/tests/system/test_base.py index 8f255b0b58d..f9f14c2f1f0 100644 --- a/libbeat/tests/system/test_base.py +++ b/libbeat/tests/system/test_base.py @@ -6,6 +6,7 @@ class Test(BaseTest): + def test_base(self): """ Basic test with exiting Mockbeat normally diff --git a/libbeat/tests/system/test_dashboard.py b/libbeat/tests/system/test_dashboard.py index 6d5f7214c0e..199f47731cb 100644 --- a/libbeat/tests/system/test_dashboard.py +++ b/libbeat/tests/system/test_dashboard.py @@ -10,6 +10,7 @@ INTEGRATION_TESTS = os.environ.get('INTEGRATION_TESTS', False) + @unittest.skip("this test will be refactored in a future commit") class Test(BaseTest): @@ -22,12 +23,14 @@ def test_load_dashboard(self): beats = ["metricbeat", "packetbeat", "filebeat", "winlogbeat"] for beat in beats: - command = "go run ../../dashboards/import_dashboards.go -es http://"+ self.get_elasticsearch_host() + " -dir ../../../"+ beat + "/_meta/kibana" + command = "go run ../../dashboards/import_dashboards.go -es http://" + \ + self.get_elasticsearch_host() + " -dir ../../../" + beat + "/_meta/kibana" if os.name == "nt": - command = "go run ..\..\dashboards\import_dashboards.go -es http:\\"+self.get_elasticsearch_host() + " -dir ..\..\..\\" + beat + "\_meta\kibana" + command = "go run ..\..\dashboards\import_dashboards.go -es http:\\" + \ + self.get_elasticsearch_host() + " -dir ..\..\..\\" + beat + "\_meta\kibana" - print command + print(command) p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) content, err = p.communicate() @@ -52,16 +55,18 @@ def test_export_dashboard(self): for beat in beats: if os.name == "nt": - path = "..\..\..\\"+ beat + "\etc\kibana" + path = "..\..\..\\" + beat + "\etc\kibana" else: - path = "../../../"+ beat + "/etc/kibana" + path = "../../../" + beat + "/etc/kibana" - command = "python ../../../dev-tools/export_dashboards.py --url http://"+ self.get_elasticsearch_host() + " --dir " + path + " --regex " + beat + "-*" + command = "python ../../../dev-tools/export_dashboards.py --url http://" + \ + self.get_elasticsearch_host() + " --dir " + path + " --regex " + beat + "-*" if os.name == "nt": - command = "python ..\..\..\dev-tools/export_dashboards.py --url http://"+ self.get_elasticsearch_host() + " --dir " + path + " --regex " + beat + "-*" + command = "python ..\..\..\dev-tools/export_dashboards.py --url http://" + \ + self.get_elasticsearch_host() + " --dir " + path + " --regex " + beat + "-*" - print command + print(command) p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) content, err = p.communicate() @@ -73,6 +78,5 @@ def test_export_dashboard(self): for f in files: self.assertIsNone(re.search('[:\>\<"/\\\|\?\*]', f)) - def get_elasticsearch_host(self): return os.getenv('ES_HOST', 'localhost') + ':' + os.getenv('ES_PORT', '9200') diff --git a/metricbeat/scripts/config_collector.py b/metricbeat/scripts/config_collector.py index b9faeee08d6..f7d62cbce71 100644 --- a/metricbeat/scripts/config_collector.py +++ b/metricbeat/scripts/config_collector.py @@ -4,6 +4,7 @@ # Collects config for all modules + def collect(beat_name, beat_path, full=False): base_dir = beat_path + "/module" @@ -63,7 +64,7 @@ def collect(beat_name, beat_path, full=False): config_yml += "\n" # output string so it can be concatenated - print config_yml + print(config_yml) # Makes sure every title line is 79 + newline chars long diff --git a/metricbeat/scripts/create_metricset.py b/metricbeat/scripts/create_metricset.py index 1f8425eb1b3..dbb87b094e9 100644 --- a/metricbeat/scripts/create_metricset.py +++ b/metricbeat/scripts/create_metricset.py @@ -94,11 +94,11 @@ def load_file(file, module, metricset): if args.path is None: args.path = './' - print "Set default path for beat path: " + args.path + print("Set default path for beat path: " + args.path) if args.es_beats is None: args.es_beats = '../' - print "Set default path for es_beats path: " + args.es_beats + print("Set default path for es_beats path: " + args.es_beats) if args.module is None or args.module == '': args.module = raw_input("Module name: ") diff --git a/metricbeat/scripts/docs_collector.py b/metricbeat/scripts/docs_collector.py index 9fb16991afb..79201545246 100644 --- a/metricbeat/scripts/docs_collector.py +++ b/metricbeat/scripts/docs_collector.py @@ -38,7 +38,7 @@ def collect(beat_name): beat_path = path + "/" + module + "/_meta" - # Load title from fields.yml + # Load title from fields.yml with open(beat_path + "/fields.yml") as f: fields = yaml.load(f.read()) title = fields[0]["title"] @@ -75,7 +75,6 @@ def collect(beat_name): module_file += "=== Metricsets\n\n" module_file += "The following metricsets are available:\n\n" - module_links = "" module_includes = "" @@ -141,7 +140,7 @@ def collect(beat_name): module_list_output += "\n\n--\n\n" for m, title in sorted(modules_list.iteritems()): - module_list_output += "include::modules/"+ m + ".asciidoc[]\n" + module_list_output += "include::modules/" + m + ".asciidoc[]\n" # Write module link list with open(os.path.abspath("docs") + "/modules_list.asciidoc", 'w') as f: @@ -157,6 +156,3 @@ def collect(beat_name): beat_name = args.beat collect(beat_name) - - - diff --git a/metricbeat/scripts/fields_collector.py b/metricbeat/scripts/fields_collector.py index 367a4e0b930..39f8c685d51 100644 --- a/metricbeat/scripts/fields_collector.py +++ b/metricbeat/scripts/fields_collector.py @@ -25,7 +25,6 @@ def collect(): tmp = f.read() fields_yml += tmp - # Iterate over all metricsets for metricset in sorted(os.listdir(base_dir + "/" + module)): @@ -48,10 +47,7 @@ def collect(): fields_yml += "\n" # output string so it can be concatenated - print fields_yml + print(fields_yml) if __name__ == "__main__": collect() - - - diff --git a/metricbeat/scripts/generate_imports.py b/metricbeat/scripts/generate_imports.py index 495a3c41fe1..2c4c2df2647 100644 --- a/metricbeat/scripts/generate_imports.py +++ b/metricbeat/scripts/generate_imports.py @@ -37,11 +37,10 @@ def generate(go_beat_path): list_file += ' _ "' + go_beat_path + '/module/' + module + '/' + metricset + '"\n' - list_file += ")" # output string so it can be concatenated - print list_file + print(list_file) if __name__ == "__main__": # First argument is the beat path under GOPATH. diff --git a/metricbeat/tests/system/metricbeat.py b/metricbeat/tests/system/metricbeat.py index f15f9cb4ada..b6c8d8af038 100644 --- a/metricbeat/tests/system/metricbeat.py +++ b/metricbeat/tests/system/metricbeat.py @@ -9,7 +9,9 @@ INTEGRATION_TESTS = os.environ.get('INTEGRATION_TESTS', False) + class BaseTest(TestCase): + @classmethod def setUpClass(self): self.beat_name = "metricbeat" diff --git a/metricbeat/tests/system/test_apache.py b/metricbeat/tests/system/test_apache.py index fdd365f7da5..281182b9d11 100644 --- a/metricbeat/tests/system/test_apache.py +++ b/metricbeat/tests/system/test_apache.py @@ -17,6 +17,7 @@ class ApacheStatusTest(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") @attr('integration') def test_output(self): @@ -36,7 +37,7 @@ def test_output(self): # Waits until CPULoad is part of the status while found == False: res = urllib2.urlopen(hosts[0] + "/server-status?auto").read() - if "CPULoad" in res: + if "CPULoad" in res: found = True time.sleep(0.5) @@ -62,7 +63,6 @@ def test_output(self): # Verify all fields present are documented. self.assert_fields_are_documented(evt) - def get_hosts(self): return ['http://' + os.getenv('APACHE_HOST', 'localhost') + ':' + os.getenv('APACHE_PORT', '80')] diff --git a/metricbeat/tests/system/test_base.py b/metricbeat/tests/system/test_base.py index 99513f50fd8..2f3d92ce909 100644 --- a/metricbeat/tests/system/test_base.py +++ b/metricbeat/tests/system/test_base.py @@ -5,6 +5,7 @@ class Test(BaseTest): + @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_start_stop(self): """ diff --git a/metricbeat/tests/system/test_config.py b/metricbeat/tests/system/test_config.py index a74bcb6622e..ef6592e6a92 100644 --- a/metricbeat/tests/system/test_config.py +++ b/metricbeat/tests/system/test_config.py @@ -7,6 +7,7 @@ class ConfigTest(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") @attr('integration') def test_compare_config(self): @@ -25,9 +26,8 @@ def test_compare_config(self): time.sleep(1) proc.check_kill_and_wait() - proc = self.start_beat(config="metricbeat.full.yml", output="full.log", - extra_args=["-E", "output.elasticsearch.hosts=['" + self.get_host() + "']"]) + extra_args=["-E", "output.elasticsearch.hosts=['" + self.get_host() + "']"]) time.sleep(1) proc.check_kill_and_wait() @@ -53,17 +53,16 @@ def test_compare_config(self): fullLine = fullLog[i] if shortLine not in fullLog: - print shortLine - print fullLine + print(shortLine) + print(fullLine) same = False if fullLine not in shortLog: - print shortLine - print fullLine + print(shortLine) + print(fullLine) same = False assert same == True - def get_host(self): return 'http://' + os.getenv('ELASTICSEARCH_HOST', 'localhost') + ':' + os.getenv('ELASTICSEARCH_PORT', '9200') diff --git a/metricbeat/tests/system/test_docker.py b/metricbeat/tests/system/test_docker.py index 5c41f247fa7..1aaff6c5b75 100644 --- a/metricbeat/tests/system/test_docker.py +++ b/metricbeat/tests/system/test_docker.py @@ -3,6 +3,7 @@ import unittest from nose.plugins.attrib import attr + class Test(metricbeat.BaseTest): @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") diff --git a/metricbeat/tests/system/test_haproxy.py b/metricbeat/tests/system/test_haproxy.py index 6af29139add..505c2759a2c 100644 --- a/metricbeat/tests/system/test_haproxy.py +++ b/metricbeat/tests/system/test_haproxy.py @@ -5,7 +5,9 @@ HAPROXY_FIELDS = metricbeat.COMMON_FIELDS + ["haproxy"] + class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") def test_info(self): """ @@ -56,11 +58,10 @@ def test_stat(self): self.assertGreater(len(output), 0) for evt in output: - print evt + print(evt) self.assertItemsEqual(self.de_dot(HAPROXY_FIELDS), evt.keys(), evt) self.assert_fields_are_documented(evt) def get_hosts(self): return ["tcp://" + os.getenv('HAPROXY_HOST', 'localhost') + ':' + os.getenv('HAPROXY_PORT', '14567')] - diff --git a/metricbeat/tests/system/test_kafka.py b/metricbeat/tests/system/test_kafka.py index b837587dfea..a86f4b36a88 100644 --- a/metricbeat/tests/system/test_kafka.py +++ b/metricbeat/tests/system/test_kafka.py @@ -5,6 +5,7 @@ class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") def test_partition(self): """ @@ -23,7 +24,7 @@ def test_partition(self): output = self.read_output_json() self.assertTrue(len(output) >= 1) evt = output[0] - print evt + print(evt) self.assert_fields_are_documented(evt) diff --git a/metricbeat/tests/system/test_mysql.py b/metricbeat/tests/system/test_mysql.py index 7d992687c4a..b2631463cd6 100644 --- a/metricbeat/tests/system/test_mysql.py +++ b/metricbeat/tests/system/test_mysql.py @@ -6,7 +6,7 @@ MYSQL_FIELDS = metricbeat.COMMON_FIELDS + ["mysql"] MYSQL_STATUS_FIELDS = ["clients", "cluster", "cpu", "keyspace", "memory", - "persistence", "replication", "server", "stats"] + "persistence", "replication", "server", "stats"] class Test(metricbeat.BaseTest): @@ -42,4 +42,3 @@ def test_status(self): def get_hosts(self): return [os.getenv('MYSQL_DSN', 'root:test@tcp(localhost:3306)/')] - diff --git a/metricbeat/tests/system/test_phpfpm.py b/metricbeat/tests/system/test_phpfpm.py index 80a81c30ca8..6d4c46a8ef1 100644 --- a/metricbeat/tests/system/test_phpfpm.py +++ b/metricbeat/tests/system/test_phpfpm.py @@ -5,7 +5,9 @@ PHPFPM_FIELDS = metricbeat.COMMON_FIELDS + ["php_fpm"] + class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") def test_info(self): """ diff --git a/metricbeat/tests/system/test_processors.py b/metricbeat/tests/system/test_processors.py index 371672c7308..9de029a3540 100644 --- a/metricbeat/tests/system/test_processors.py +++ b/metricbeat/tests/system/test_processors.py @@ -3,6 +3,7 @@ import metricbeat import unittest + @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") class TestProcessors(metricbeat.BaseTest): @@ -15,7 +16,7 @@ def test_drop_fields(self): "period": "1s" }], processors=[{ - "drop_fields":{ + "drop_fields": { "when": "range.system.cpu.system.pct.lt: 0.1", "fields": ["system.cpu.load"], }, @@ -43,7 +44,6 @@ def test_drop_fields(self): "idle", "irq", "steal", "nice" ]), cpu.keys()) - def test_dropfields_with_condition(self): """ Check drop_fields action works when a condition is associated. @@ -55,7 +55,7 @@ def test_dropfields_with_condition(self): "period": "1s" }], processors=[{ - "drop_fields":{ + "drop_fields": { "fields": ["system.process.memory"], "when": "range.system.process.cpu.total.pct.lt: 0.5", }, @@ -89,7 +89,7 @@ def test_dropevent_with_condition(self): "period": "1s" }], processors=[{ - "drop_event":{ + "drop_event": { "when": "range.system.process.cpu.total.pct.lt: 0.001", }, }] @@ -107,7 +107,6 @@ def test_dropevent_with_condition(self): for event in output: assert float(event["system.process.cpu.total.pct"]) >= 0.001 - def test_dropevent_with_complex_condition(self): """ Check drop_event action works when a complex condition is associated. @@ -119,7 +118,7 @@ def test_dropevent_with_complex_condition(self): "period": "1s" }], processors=[{ - "drop_event":{ + "drop_event": { "when.not": "contains.system.process.cmdline: metricbeat.test", }, }] @@ -136,7 +135,6 @@ def test_dropevent_with_complex_condition(self): ) assert len(output) >= 1 - def test_include_fields(self): """ Check include_fields filtering action @@ -148,7 +146,7 @@ def test_include_fields(self): "period": "1s" }], processors=[{ - "include_fields":{"fields": ["system.process.cpu", "system.process.memory"]}, + "include_fields": {"fields": ["system.process.cpu", "system.process.memory"]}, }] ) metricbeat = self.start_beat() @@ -190,7 +188,7 @@ def test_multiple_actions(self): "period": "1s" }], processors=[{ - "include_fields":{"fields": ["system.process"]}, + "include_fields": {"fields": ["system.process"]}, }, { "drop_fields": {"fields": ["system.process.memory"]}, }] @@ -232,7 +230,7 @@ def test_contradictory_multiple_actions(self): "period": "1s" }], processors=[{ - "include_fields":{ + "include_fields": { "fields": ["system.process.memory.size", "proc.memory.rss.pct"], }, }, { diff --git a/metricbeat/tests/system/test_prometheus.py b/metricbeat/tests/system/test_prometheus.py index b9b0fbcfa2e..972d659ccc9 100644 --- a/metricbeat/tests/system/test_prometheus.py +++ b/metricbeat/tests/system/test_prometheus.py @@ -5,7 +5,9 @@ PROMETHEUS_FIELDS = metricbeat.COMMON_FIELDS + ["prometheus"] + class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") def test_stats(self): """ diff --git a/metricbeat/tests/system/test_redis.py b/metricbeat/tests/system/test_redis.py index f6869822467..21438252a01 100644 --- a/metricbeat/tests/system/test_redis.py +++ b/metricbeat/tests/system/test_redis.py @@ -19,6 +19,7 @@ class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") @attr('integration') def test_info(self): @@ -114,7 +115,7 @@ def test_filters(self): self.assertItemsEqual(self.de_dot(REDIS_FIELDS), evt.keys()) redis_info = evt["redis"]["info"] - print redis_info + print(redis_info) self.assertItemsEqual(fields, redis_info.keys()) self.assertItemsEqual(self.de_dot(CLIENTS_FIELDS), redis_info["clients"].keys()) self.assertItemsEqual(self.de_dot(CPU_FIELDS), redis_info["cpu"].keys()) @@ -122,4 +123,3 @@ def test_filters(self): def get_hosts(self): return [os.getenv('REDIS_HOST', 'localhost') + ':' + os.getenv('REDIS_PORT', '6379')] - diff --git a/metricbeat/tests/system/test_reload.py b/metricbeat/tests/system/test_reload.py index 85a4ffa9643..40a88ba0ca4 100644 --- a/metricbeat/tests/system/test_reload.py +++ b/metricbeat/tests/system/test_reload.py @@ -15,6 +15,7 @@ # * Test empty file class Test(metricbeat.BaseTest): + @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_reload(self): """ @@ -26,10 +27,8 @@ def test_reload(self): ) proc = self.start_beat() - os.mkdir(self.working_dir + "/configs/") - systemConfig = """ - module: system metricsets: ["cpu"] @@ -42,7 +41,6 @@ def test_reload(self): self.wait_until(lambda: self.output_lines() > 0) proc.check_kill_and_wait() - @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_start_stop(self): """ @@ -88,5 +86,4 @@ def test_start_stop(self): # Make sure no new lines were added since stopping assert lines == self.output_lines() - proc.check_kill_and_wait() diff --git a/metricbeat/tests/system/test_system.py b/metricbeat/tests/system/test_system.py index d0f8856c1de..5e0b1eb2ab0 100644 --- a/metricbeat/tests/system/test_system.py +++ b/metricbeat/tests/system/test_system.py @@ -9,18 +9,18 @@ "softirq.pct", "steal.pct", "system.pct", "user.pct"] SYSTEM_CPU_FIELDS_ALL = ["cores", "idle.pct", "idle.ticks", "iowait.pct", "iowait.ticks", "irq.pct", "irq.ticks", "nice.pct", "nice.ticks", - "softirq.pct", "softirq.ticks", "steal.pct", "steal.ticks", "system.pct", "system.ticks", "user.pct", "user.ticks"] + "softirq.pct", "softirq.ticks", "steal.pct", "steal.ticks", "system.pct", "system.ticks", "user.pct", "user.ticks"] SYSTEM_LOAD_FIELDS = ["1", "5", "15", "norm.1", "norm.5", "norm.15"] SYSTEM_CORE_FIELDS = ["id", "idle.pct", "iowait.pct", "irq.pct", "nice.pct", - "softirq.pct", "steal.pct", "system.pct", "user.pct"] + "softirq.pct", "steal.pct", "system.pct", "user.pct"] SYSTEM_CORE_FIELDS_ALL = SYSTEM_CORE_FIELDS + ["idle.ticks", "iowait.ticks", "irq.ticks", "nice.ticks", - "softirq.ticks", "steal.ticks", "system.ticks", "user.ticks"] + "softirq.ticks", "steal.ticks", "system.ticks", "user.ticks"] SYSTEM_DISKIO_FIELDS = ["name", "read.count", "write.count", "read.bytes", - "write.bytes", "read.time", "write.time", "io.time"] + "write.bytes", "read.time", "write.time", "io.time"] SYSTEM_FILESYSTEM_FIELDS = ["available", "device_name", "files", "free", "free_files", "mount_point", "total", "used.bytes", @@ -42,6 +42,7 @@ class SystemTest(metricbeat.BaseTest): + @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_cpu(self): """ @@ -372,7 +373,7 @@ def test_process(self): self.assertTrue(found_fd, "fd not found in any process events") if sys.platform.startswith("linux") or sys.platform.startswith("freebsd")\ - or sys.platform.startswith("darwin"): + or sys.platform.startswith("darwin"): self.assertTrue(found_env, "env not found in any process events") @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") @@ -400,7 +401,7 @@ def test_process_metricbeat(self): assert isinstance(output["system.process.cpu.start_time"], basestring) self.check_username(output["system.process.username"]) - def check_username(self, observed, expected = None): + def check_username(self, observed, expected=None): if expected == None: expected = getpass.getuser() diff --git a/metricbeat/tests/system/test_zookeeper.py b/metricbeat/tests/system/test_zookeeper.py index 44387084fd7..7975a1eda39 100644 --- a/metricbeat/tests/system/test_zookeeper.py +++ b/metricbeat/tests/system/test_zookeeper.py @@ -11,7 +11,9 @@ "watch_count", "ephemerals_count", "approximate_data_size", "num_alive_connections"] + class ZooKeeperMntrTest(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") @attr('integration') def test_output(self): @@ -49,9 +51,6 @@ def test_output(self): self.assert_fields_are_documented(evt) - def get_hosts(self): return [os.getenv('ZOOKEEPER_HOST', 'localhost') + ':' + os.getenv('ZOOKEEPER_PORT', '2181')] - - diff --git a/packetbeat/scripts/create_tcp_protocol.py b/packetbeat/scripts/create_tcp_protocol.py index d119e988009..444aa8d2c7f 100644 --- a/packetbeat/scripts/create_tcp_protocol.py +++ b/packetbeat/scripts/create_tcp_protocol.py @@ -7,10 +7,12 @@ plugin_type = "" plugin_var = "" + def generate_protocol(): read_input() process_file() + def read_input(): """Requests input form the command line for empty variables if needed. """ @@ -37,7 +39,7 @@ def process_file(): full_path = root + "/" + file - ## load file + # load file content = "" with open(full_path) as f: content = f.read() @@ -63,6 +65,7 @@ def process_file(): with open(write_file, 'w') as f: f.write(content) + def replace_variables(content): """Replace all template variables with the actual values """ @@ -78,10 +81,7 @@ def replace_variables(content): args = parser.parse_args() - if args.protocol is not None: protocol = args.protocol generate_protocol() - - diff --git a/packetbeat/scripts/generate_imports.py b/packetbeat/scripts/generate_imports.py index 2c9d5d0f94e..3881040740f 100644 --- a/packetbeat/scripts/generate_imports.py +++ b/packetbeat/scripts/generate_imports.py @@ -32,7 +32,7 @@ def generate(go_beat_path): list_file += ")" # output string so it can be concatenated - print list_file + print(list_file) if __name__ == "__main__": # First argument is the beat path under GOPATH. diff --git a/packetbeat/tests/system/gen/memcache/tcp_multi_store_load.py b/packetbeat/tests/system/gen/memcache/tcp_multi_store_load.py index d23f1cba331..48241fd9ddf 100755 --- a/packetbeat/tests/system/gen/memcache/tcp_multi_store_load.py +++ b/packetbeat/tests/system/gen/memcache/tcp_multi_store_load.py @@ -7,9 +7,9 @@ def run(mc): print('run') res = mc.set_multi({ - "k1": 100*'a', - "k2": 20*'b', - "k3": 10*'c', + "k1": 100 * 'a', + "k2": 20 * 'b', + "k3": 10 * 'c', }) print(res) if len(res) > 0: diff --git a/packetbeat/tests/system/gen/memcache/tcp_single_load_store.py b/packetbeat/tests/system/gen/memcache/tcp_single_load_store.py index 8ca849ffcdf..5c0bed24bbc 100755 --- a/packetbeat/tests/system/gen/memcache/tcp_single_load_store.py +++ b/packetbeat/tests/system/gen/memcache/tcp_single_load_store.py @@ -7,7 +7,7 @@ def run(mc): print('run') # write 2kb entry - v = 2046*'a' + v = 2046 * 'a' if not mc.set('test_key', v): raise RuntimeError("failed to set value") diff --git a/packetbeat/tests/system/gen/memcache/udp_multi_store.py b/packetbeat/tests/system/gen/memcache/udp_multi_store.py index d54e4cbe512..58223fa1948 100755 --- a/packetbeat/tests/system/gen/memcache/udp_multi_store.py +++ b/packetbeat/tests/system/gen/memcache/udp_multi_store.py @@ -7,9 +7,9 @@ def run(mc): print('run') res = mc.set_multi({ - "k1": 100*'a', - "k2": 20*'b', - "k3": 10*'c', + "k1": 100 * 'a', + "k2": 20 * 'b', + "k3": 10 * 'c', }) print(res) diff --git a/packetbeat/tests/system/gen/memcache/udp_single_store.py b/packetbeat/tests/system/gen/memcache/udp_single_store.py index 0495946fd2c..248a7287377 100755 --- a/packetbeat/tests/system/gen/memcache/udp_single_store.py +++ b/packetbeat/tests/system/gen/memcache/udp_single_store.py @@ -7,7 +7,7 @@ def run(mc): print('run') # write 1kb entry - v = 1024*'a' + v = 1024 * 'a' if not mc.set('test_key', v): raise RuntimeError("failed to set value") diff --git a/packetbeat/tests/system/packetbeat.py b/packetbeat/tests/system/packetbeat.py index d30e20fb191..fa8d5234c25 100644 --- a/packetbeat/tests/system/packetbeat.py +++ b/packetbeat/tests/system/packetbeat.py @@ -61,7 +61,8 @@ def run_packetbeat(self, pcap, stdout=outputfile, stderr=subprocess.STDOUT) actual_exit_code = proc.wait() - assert actual_exit_code == exit_code, "Expected exit code to be %d, but it was %d" % (exit_code, actual_exit_code) + assert actual_exit_code == exit_code, "Expected exit code to be %d, but it was %d" % ( + exit_code, actual_exit_code) return actual_exit_code def start_packetbeat(self, @@ -106,11 +107,10 @@ def read_output(self, self.all_fields_are_expected(jsons, self.expected_fields) return jsons - def setUp(self): self.template_env = jinja2.Environment( - loader=jinja2.FileSystemLoader("config") + loader=jinja2.FileSystemLoader("config") ) # create working dir diff --git a/packetbeat/tests/system/test_0001_mysql_spaces.py b/packetbeat/tests/system/test_0001_mysql_spaces.py index c9ad004d210..e763980fb24 100644 --- a/packetbeat/tests/system/test_0001_mysql_spaces.py +++ b/packetbeat/tests/system/test_0001_mysql_spaces.py @@ -2,6 +2,7 @@ class Test(BaseTest): + def test_mysql_with_spaces(self): self.render_config_template( mysql_ports=[3306] diff --git a/packetbeat/tests/system/test_0006_wsgi.py b/packetbeat/tests/system/test_0006_wsgi.py index 0e844596527..a792469e29e 100644 --- a/packetbeat/tests/system/test_0006_wsgi.py +++ b/packetbeat/tests/system/test_0006_wsgi.py @@ -138,7 +138,7 @@ def test_split_cookie(self): objs = self.read_output() assert len(objs) == 1 o = objs[0] - print o + print(o) assert len(o["http.request.headers"]) > 0 assert len(o["http.response.headers"]) > 0 diff --git a/packetbeat/tests/system/test_0009_pgsql.py b/packetbeat/tests/system/test_0009_pgsql.py index be1cd1c2742..413641284b9 100644 --- a/packetbeat/tests/system/test_0009_pgsql.py +++ b/packetbeat/tests/system/test_0009_pgsql.py @@ -2,6 +2,7 @@ class Test(BaseTest): + def test_select(self): self.render_config_template( pgsql_ports=[5432], diff --git a/packetbeat/tests/system/test_0009_pgsql_extended_query.py b/packetbeat/tests/system/test_0009_pgsql_extended_query.py index d8b07b3f1fd..d39e2f40669 100644 --- a/packetbeat/tests/system/test_0009_pgsql_extended_query.py +++ b/packetbeat/tests/system/test_0009_pgsql_extended_query.py @@ -2,6 +2,7 @@ class Test(BaseTest): + def test_extended_query(self): self.render_config_template( pgsql_ports=[5432] @@ -16,4 +17,3 @@ def test_extended_query(self): assert o["query"] == "SELECT * from test where id = $1" assert o["bytes_in"] == 90 assert o["bytes_out"] == 101 - diff --git a/packetbeat/tests/system/test_0012_http_basicauth.py b/packetbeat/tests/system/test_0012_http_basicauth.py index 9cae8aa6ab8..db06f5cb6c2 100644 --- a/packetbeat/tests/system/test_0012_http_basicauth.py +++ b/packetbeat/tests/system/test_0012_http_basicauth.py @@ -5,6 +5,7 @@ Tests for removing base64-encoded authentication information """ + class Test(BaseTest): def test_http_auth_headers(self): @@ -22,8 +23,7 @@ def test_http_auth_headers(self): assert len(objs) >= 1 assert all([o["type"] == "http" for o in objs]) assert all([o["http.request.headers"]["authorization"] == "*" - is not None for o in objs]) - + is not None for o in objs]) def test_http_auth_raw(self): self.render_config_template( diff --git a/packetbeat/tests/system/test_0015_udpjson.py b/packetbeat/tests/system/test_0015_udpjson.py index 8da728232a9..454dd3a3523 100644 --- a/packetbeat/tests/system/test_0015_udpjson.py +++ b/packetbeat/tests/system/test_0015_udpjson.py @@ -4,6 +4,7 @@ class Test(BaseTest): + @nottest def test_udpjson_config(self): """ diff --git a/packetbeat/tests/system/test_0017_mysql_long_result.py b/packetbeat/tests/system/test_0017_mysql_long_result.py index 1a4a1243219..eb5c1885d36 100644 --- a/packetbeat/tests/system/test_0017_mysql_long_result.py +++ b/packetbeat/tests/system/test_0017_mysql_long_result.py @@ -27,7 +27,7 @@ def test_default_settings(self): assert len(lines) == 11 # 10 plus header for line in lines[3:]: - print len(line) + print(len(line)) assert len(line) == 261 def test_max_row_length(self): diff --git a/packetbeat/tests/system/test_0018_pgsql_long_result.py b/packetbeat/tests/system/test_0018_pgsql_long_result.py index 69ed981b8d5..c16659af3b5 100644 --- a/packetbeat/tests/system/test_0018_pgsql_long_result.py +++ b/packetbeat/tests/system/test_0018_pgsql_long_result.py @@ -27,7 +27,7 @@ def test_default_settings(self): assert len(lines) == 11 # 10 plus header for line in lines[4:]: - print line, len(line) + print(line, len(line)) assert len(line) == 237 def test_max_row_length(self): @@ -51,7 +51,7 @@ def test_max_row_length(self): assert len(lines) == 11 # 10 plus header for line in lines[4:]: - print line, len(line) + print(line, len(line)) assert len(line) == 83 # 79 plus two separators and two quotes def test_max_rows(self): diff --git a/packetbeat/tests/system/test_0023_http_params.py b/packetbeat/tests/system/test_0023_http_params.py index 22a7821472d..ba989e0285d 100644 --- a/packetbeat/tests/system/test_0023_http_params.py +++ b/packetbeat/tests/system/test_0023_http_params.py @@ -21,7 +21,7 @@ def test_http_post(self): assert o["type"] == "http" assert len(o["http.request.params"]) > 0 assert o["http.request.params"] == "address=anklamerstr.14b&telephon=8932784368&" +\ - "user=monica" + "user=monica" def test_http_get(self): """ @@ -34,7 +34,7 @@ def test_http_get(self): assert len(objs) == 1 o = objs[0] - print o + print(o) assert o["type"] == "http" assert len(o["http.request.params"]) > 0 assert o["http.request.params"] == "input=packetbeat&src_ip=192.35.243.1" diff --git a/packetbeat/tests/system/test_0025_mongodb_basic.py b/packetbeat/tests/system/test_0025_mongodb_basic.py index 82ecfd30fbb..81ee034073d 100644 --- a/packetbeat/tests/system/test_0025_mongodb_basic.py +++ b/packetbeat/tests/system/test_0025_mongodb_basic.py @@ -180,7 +180,7 @@ def test_session(self): debug_selectors=["mongodb"]) objs = self.read_output() - print len(objs) + print(len(objs)) assert len([o for o in objs if o["method"] == "insert"]) == 2 assert len([o for o in objs if o["method"] == "update"]) == 1 assert len([o for o in objs if o["method"] == "findandmodify"]) == 1 diff --git a/packetbeat/tests/system/test_0029_http_gap.py b/packetbeat/tests/system/test_0029_http_gap.py index 4a6ca89f7e3..28a41c14e58 100644 --- a/packetbeat/tests/system/test_0029_http_gap.py +++ b/packetbeat/tests/system/test_0029_http_gap.py @@ -22,6 +22,6 @@ def test_gap_in_large_file(self): o = objs[0] assert o["status"] == "OK" - print o["notes"] + print(o["notes"]) assert len(o["notes"]) == 1 assert o["notes"][0] == "Packet loss while capturing the response" diff --git a/packetbeat/tests/system/test_0030_mysql_gap.py b/packetbeat/tests/system/test_0030_mysql_gap.py index 1e4c263a8a4..0b2d8e46242 100644 --- a/packetbeat/tests/system/test_0030_mysql_gap.py +++ b/packetbeat/tests/system/test_0030_mysql_gap.py @@ -25,6 +25,6 @@ def test_gap_in_large_file(self): assert o["method"] == "SELECT" assert o["mysql.num_rows"] > 1 - print o["notes"] + print(o["notes"]) assert len(o["notes"]) == 1 assert o["notes"][0] == "Packet loss while capturing the response" diff --git a/packetbeat/tests/system/test_0031_vlans.py b/packetbeat/tests/system/test_0031_vlans.py index 0a463244094..41ca0ffa834 100644 --- a/packetbeat/tests/system/test_0031_vlans.py +++ b/packetbeat/tests/system/test_0031_vlans.py @@ -6,6 +6,7 @@ class Test(BaseTest): + def test_http_vlan(self): """ Should extract a request/response that have vlan tags. diff --git a/packetbeat/tests/system/test_0032_dns.py b/packetbeat/tests/system/test_0032_dns.py index d31c86d38a2..ef01af80a5f 100644 --- a/packetbeat/tests/system/test_0032_dns.py +++ b/packetbeat/tests/system/test_0032_dns.py @@ -6,6 +6,7 @@ class Test(BaseTest): + def test_A(self): """ Should correctly interpret an A query to google.com diff --git a/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py b/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py index aad4d2bafeb..48cc0be3a0d 100644 --- a/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py +++ b/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py @@ -10,6 +10,7 @@ class Test(BaseTest): + def _run(self, pcap): self.render_config_template() self.run_packetbeat(pcap=pcap, diff --git a/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py b/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py index 257aac36037..5f22019f2af 100644 --- a/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py +++ b/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py @@ -11,6 +11,7 @@ class Test(BaseTest): + def _run(self, pcap): self.render_config_template() self.run_packetbeat(pcap=pcap, diff --git a/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py b/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py index c66cfef1106..4a23cf84b73 100644 --- a/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py +++ b/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py @@ -11,6 +11,7 @@ def pretty(*k, **kw): class Test(BaseTest): + def _run(self, pcap): self.render_config_template( memcache_udp_transaction_timeout=10 diff --git a/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py b/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py index 2f36eec1fde..30ff2e3558d 100644 --- a/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py +++ b/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py @@ -11,6 +11,7 @@ class Test(BaseTest): + def _run(self, pcap): self.render_config_template( memcache_udp_transaction_timeout=10 diff --git a/packetbeat/tests/system/test_0060_flows.py b/packetbeat/tests/system/test_0060_flows.py index ee04aab7554..1dc02a295a0 100644 --- a/packetbeat/tests/system/test_0060_flows.py +++ b/packetbeat/tests/system/test_0060_flows.py @@ -12,6 +12,7 @@ def check_fields(flow, fields): class Test(BaseTest): + def test_mysql_flow(self): self.render_config_template( flows=True, diff --git a/packetbeat/tests/system/test_0061_nfs.py b/packetbeat/tests/system/test_0061_nfs.py index 25882ff0671..2bf63cf14fc 100644 --- a/packetbeat/tests/system/test_0061_nfs.py +++ b/packetbeat/tests/system/test_0061_nfs.py @@ -6,6 +6,7 @@ class Test(BaseTest): + def test_V3(self): """ Should correctly parse NFS v3 packet diff --git a/packetbeat/tests/system/test_0062_cassandra.py b/packetbeat/tests/system/test_0062_cassandra.py index 5e27ebe656c..c6ca2b27b06 100644 --- a/packetbeat/tests/system/test_0062_cassandra.py +++ b/packetbeat/tests/system/test_0062_cassandra.py @@ -18,25 +18,25 @@ def test_create_keyspace(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_create_keyspace.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_create_keyspace.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" assert o["port"] == 9042 - assert o["cassandra.request.query"] == "CREATE KEYSPACE mykeyspace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };" + assert o[ + "cassandra.request.query"] == "CREATE KEYSPACE mykeyspace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };" assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "QUERY" assert o["cassandra.request.headers.length"] == 124 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 20 - - assert o["cassandra.response.result.type"]=="schemaChanged" - assert o["cassandra.response.result.schema_change.change"]=="CREATED" - assert o["cassandra.response.result.schema_change.keyspace"]=="mykeyspace" - assert o["cassandra.response.result.schema_change.target"]=="KEYSPACE" + assert o["cassandra.response.result.type"] == "schemaChanged" + assert o["cassandra.response.result.schema_change.change"] == "CREATED" + assert o["cassandra.response.result.schema_change.keyspace"] == "mykeyspace" + assert o["cassandra.response.result.schema_change.target"] == "KEYSPACE" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 35 @@ -55,13 +55,14 @@ def test_create_table(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_create_table.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_create_table.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" assert o["port"] == 9042 - assert o["cassandra.request.query"] == "CREATE TABLE users (\n user_id int PRIMARY KEY,\n fname text,\n lname text\n);" + assert o[ + "cassandra.request.query"] == "CREATE TABLE users (\n user_id int PRIMARY KEY,\n fname text,\n lname text\n);" assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "QUERY" @@ -69,8 +70,7 @@ def test_create_table(self): assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 49 - - assert o["cassandra.response.result.type"]=="schemaChanged" + assert o["cassandra.response.result.type"] == "schemaChanged" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 39 assert o["cassandra.response.headers.op"] == "RESULT" @@ -88,21 +88,22 @@ def test_insert_data(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_insert.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_insert.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 - assert o["cassandra.request.query"] == "INSERT INTO users (user_id, fname, lname)\n VALUES (1745, 'john', 'smith');" + assert o[ + "cassandra.request.query"] == "INSERT INTO users (user_id, fname, lname)\n VALUES (1745, 'john', 'smith');" assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "QUERY" assert o["cassandra.request.headers.length"] == 97 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 252 - assert o["cassandra.response.result.type"]=="void" + assert o["cassandra.response.result.type"] == "void" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 4 assert o["cassandra.response.headers.op"] == "RESULT" @@ -120,7 +121,7 @@ def test_select_data(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_select.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_select.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" @@ -133,8 +134,7 @@ def test_select_data(self): assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 253 - - assert o["cassandra.response.result.type"]=="rows" + assert o["cassandra.response.result.type"] == "rows" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 89 assert o["cassandra.response.headers.op"] == "RESULT" @@ -152,7 +152,7 @@ def test_create_index(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_create_index.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_create_index.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" @@ -165,7 +165,7 @@ def test_create_index(self): assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 92 - assert o["cassandra.response.result.type"]=="schemaChanged" + assert o["cassandra.response.result.type"] == "schemaChanged" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 39 @@ -184,7 +184,7 @@ def test_trace_error(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_trace_err.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_trace_err.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" @@ -193,7 +193,7 @@ def test_trace_error(self): assert o["bytes_in"] == 55 assert o["bytes_out"] == 62 assert o["cassandra.request.query"] == "DROP KEYSPACE mykeyspace;" - print o + print(o) assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "QUERY" @@ -201,9 +201,9 @@ def test_trace_error(self): assert o["cassandra.request.headers.flags"] == "Tracing" assert o["cassandra.request.headers.stream"] == 275 - assert o["cassandra.response.error.code"]==8960 - assert o["cassandra.response.error.msg"]=="Cannot drop non existing keyspace 'mykeyspace'." - assert o["cassandra.response.error.type"]=="errConfig" + assert o["cassandra.response.error.code"] == 8960 + assert o["cassandra.response.error.msg"] == "Cannot drop non existing keyspace 'mykeyspace'." + assert o["cassandra.response.error.type"] == "errConfig" assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 53 @@ -222,10 +222,10 @@ def test_select_use_index(self): cassandra_send_request_header=True, cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_select_via_index.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_select_via_index.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 @@ -237,14 +237,12 @@ def test_select_use_index(self): assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 262 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 89 assert o["cassandra.response.headers.op"] == "RESULT" assert o["cassandra.response.headers.flags"] == "Default" assert o["cassandra.response.headers.stream"] == 262 - assert o["cassandra.response.result.type"] =="rows" + assert o["cassandra.response.result.type"] == "rows" def test_ops_mixed(self): """ @@ -258,25 +256,22 @@ def test_ops_mixed(self): cassandra_send_response_header=True, ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_mixed_frame.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_mixed_frame.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 9 assert o["bytes_out"] == 61 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "OPTIONS" assert o["cassandra.request.headers.length"] == 0 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 0 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 52 assert o["cassandra.response.headers.op"] == "SUPPORTED" @@ -284,21 +279,18 @@ def test_ops_mixed(self): assert o["cassandra.response.headers.stream"] == 0 o = objs[1] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 31 assert o["bytes_out"] == 9 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "STARTUP" assert o["cassandra.request.headers.length"] == 22 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 1 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 0 assert o["cassandra.response.headers.op"] == "READY" @@ -306,21 +298,18 @@ def test_ops_mixed(self): assert o["cassandra.response.headers.stream"] == 1 o = objs[2] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 58 assert o["bytes_out"] == 9 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "REGISTER" assert o["cassandra.request.headers.length"] == 49 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 2 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 0 assert o["cassandra.response.headers.op"] == "READY" @@ -337,10 +326,10 @@ def test_ops_ignored(self): cassandra_send_response=True, cassandra_send_request_header=True, cassandra_send_response_header=True, - cassandra_ignored_ops= ["OPTIONS","REGISTER"] + cassandra_ignored_ops=["OPTIONS", "REGISTER"] ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_mixed_frame.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_mixed_frame.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] @@ -349,15 +338,12 @@ def test_ops_ignored(self): assert o["bytes_in"] == 31 assert o["bytes_out"] == 9 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "STARTUP" assert o["cassandra.request.headers.length"] == 22 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 1 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 0 assert o["cassandra.response.headers.op"] == "READY" @@ -370,15 +356,12 @@ def test_ops_ignored(self): assert o["bytes_in"] == 101 assert o["bytes_out"] == 116 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "QUERY" assert o["cassandra.request.headers.length"] == 92 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 3 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 107 assert o["cassandra.response.headers.op"] == "RESULT" @@ -395,28 +378,25 @@ def test_compressed_frame(self): cassandra_send_response=True, cassandra_send_request_header=True, cassandra_send_response_header=True, - cassandra_compressor= "snappy", + cassandra_compressor="snappy", ) - self.run_packetbeat(pcap="cassandra/v4/cassandra_compressed.pcap",debug_selectors=["*"]) + self.run_packetbeat(pcap="cassandra/v4/cassandra_compressed.pcap", debug_selectors=["*"]) objs = self.read_output() o = objs[0] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 52 assert o["bytes_out"] == 10 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "STARTUP" assert o["cassandra.request.headers.length"] == 43 assert o["cassandra.request.headers.flags"] == "Default" assert o["cassandra.request.headers.stream"] == 0 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 1 assert o["cassandra.response.headers.op"] == "READY" @@ -424,21 +404,18 @@ def test_compressed_frame(self): assert o["cassandra.response.headers.stream"] == 0 o = objs[1] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 53 assert o["bytes_out"] == 10 - assert o["cassandra.request.headers.version"] == "4" assert o["cassandra.request.headers.op"] == "REGISTER" assert o["cassandra.request.headers.length"] == 44 assert o["cassandra.request.headers.flags"] == "Compress" assert o["cassandra.request.headers.stream"] == 64 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 1 assert o["cassandra.response.headers.op"] == "READY" @@ -446,7 +423,7 @@ def test_compressed_frame(self): assert o["cassandra.response.headers.stream"] == 64 o = objs[2] - print o + print(o) assert o["type"] == "cassandra" assert o["port"] == 9042 assert o["bytes_in"] == 62 @@ -460,8 +437,6 @@ def test_compressed_frame(self): assert o["cassandra.request.headers.flags"] == "Compress" assert o["cassandra.request.headers.stream"] == 0 - - assert o["cassandra.response.headers.version"] == "4" assert o["cassandra.response.headers.length"] == 156 assert o["cassandra.response.headers.op"] == "RESULT" @@ -473,4 +448,3 @@ def test_compressed_frame(self): assert o["cassandra.response.result.rows.meta.flags"] == "GlobalTableSpec" assert o["cassandra.response.result.rows.meta.keyspace"] == "system" assert o["cassandra.response.result.rows.meta.table"] == "peers" - diff --git a/packetbeat/tests/system/test_0063_http_body.py b/packetbeat/tests/system/test_0063_http_body.py index 4beede566cf..1b1035c354b 100644 --- a/packetbeat/tests/system/test_0063_http_body.py +++ b/packetbeat/tests/system/test_0063_http_body.py @@ -77,7 +77,7 @@ def test_wrong_content_type(self): assert len(objs) == 1 o = objs[0] - print o + print(o) assert o["type"] == "http" @@ -107,7 +107,7 @@ def test_large_body(self): assert len(objs) == 1 o = objs[0] - print len(o["http.response.body"]) + print(len(o["http.response.body"])) # response body should be included but trimmed assert len(o["http.response.body"]) < 2000 diff --git a/vendor/github.com/tsg/gopacket/layers/test_creator.py b/vendor/github.com/tsg/gopacket/layers/test_creator.py index c92d2765a22..3a506ff189e 100755 --- a/vendor/github.com/tsg/gopacket/layers/test_creator.py +++ b/vendor/github.com/tsg/gopacket/layers/test_creator.py @@ -13,91 +13,91 @@ class Packet(object): - """Helper class encapsulating packet from a pcap file.""" - - def __init__(self, packet_lines): - self.packet_lines = packet_lines - self.data = self._DecodeText(packet_lines) - - @classmethod - def _DecodeText(cls, packet_lines): - packet_bytes = [] - # First line is timestamp and stuff, skip it. - # Format: 0x0010: 0000 0020 3aff 3ffe 0000 0000 0000 0000 ....:.?......... - - for line in packet_lines[1:]: - m = re.match(r'\s+0x[a-f\d]+:\s+((?:[\da-f]{2,4}\s)*)', line, re.IGNORECASE) - if m is None: continue - for hexpart in m.group(1).split(): - packet_bytes.append(base64.b16decode(hexpart.upper())) - return ''.join(packet_bytes) - - def Test(self, name, link_type): - """Yields a test using this packet, as a set of lines.""" - yield '// testPacket%s is the packet:' % name - for line in self.packet_lines: - yield '// ' + line - yield 'var testPacket%s = []byte{' % name - data = list(self.data) - while data: - linebytes, data = data[:16], data[16:] - yield ''.join(['\t'] + ['0x%02x, ' % ord(c) for c in linebytes]) - yield '}' - yield 'func TestPacket%s(t *testing.T) {' % name - yield '\tp := gopacket.NewPacket(testPacket%s, LinkType%s, gopacket.Default)' % (name, link_type) - yield '\tif p.ErrorLayer() != nil {' - yield '\t\tt.Error("Failed to decode packet:", p.ErrorLayer().Error())' - yield '\t}' - yield '\tcheckLayers(p, []gopacket.LayerType{LayerType%s, FILL_ME_IN_WITH_ACTUAL_LAYERS}, t)' % link_type - yield '}' - yield 'func BenchmarkDecodePacket%s(b *testing.B) {' % name - yield '\tfor i := 0; i < b.N; i++ {' - yield '\t\tgopacket.NewPacket(testPacket%s, LinkType%s, gopacket.NoCopy)' % (name, link_type) - yield '\t}' - yield '}' - + """Helper class encapsulating packet from a pcap file.""" + + def __init__(self, packet_lines): + self.packet_lines = packet_lines + self.data = self._DecodeText(packet_lines) + + @classmethod + def _DecodeText(cls, packet_lines): + packet_bytes = [] + # First line is timestamp and stuff, skip it. + # Format: 0x0010: 0000 0020 3aff 3ffe 0000 0000 0000 0000 ....:.?......... + + for line in packet_lines[1:]: + m = re.match(r'\s+0x[a-f\d]+:\s+((?:[\da-f]{2,4}\s)*)', line, re.IGNORECASE) + if m is None: + continue + for hexpart in m.group(1).split(): + packet_bytes.append(base64.b16decode(hexpart.upper())) + return ''.join(packet_bytes) + + def Test(self, name, link_type): + """Yields a test using this packet, as a set of lines.""" + yield '// testPacket%s is the packet:' % name + for line in self.packet_lines: + yield '// ' + line + yield 'var testPacket%s = []byte{' % name + data = list(self.data) + while data: + linebytes, data = data[:16], data[16:] + yield ''.join(['\t'] + ['0x%02x, ' % ord(c) for c in linebytes]) + yield '}' + yield 'func TestPacket%s(t *testing.T) {' % name + yield '\tp := gopacket.NewPacket(testPacket%s, LinkType%s, gopacket.Default)' % (name, link_type) + yield '\tif p.ErrorLayer() != nil {' + yield '\t\tt.Error("Failed to decode packet:", p.ErrorLayer().Error())' + yield '\t}' + yield '\tcheckLayers(p, []gopacket.LayerType{LayerType%s, FILL_ME_IN_WITH_ACTUAL_LAYERS}, t)' % link_type + yield '}' + yield 'func BenchmarkDecodePacket%s(b *testing.B) {' % name + yield '\tfor i := 0; i < b.N; i++ {' + yield '\t\tgopacket.NewPacket(testPacket%s, LinkType%s, gopacket.NoCopy)' % (name, link_type) + yield '\t}' + yield '}' def GetTcpdumpOutput(filename): - """Runs tcpdump on the given file, returning output as string.""" - return subprocess.check_output( - ['tcpdump', '-XX', '-s', '0', '-n', '-r', filename]) + """Runs tcpdump on the given file, returning output as string.""" + return subprocess.check_output( + ['tcpdump', '-XX', '-s', '0', '-n', '-r', filename]) def TcpdumpOutputToPackets(output): - """Reads a pcap file with TCPDump, yielding Packet objects.""" - pdata = [] - for line in output.splitlines(): - if line[0] not in string.whitespace and pdata: - yield Packet(pdata) - pdata = [] - pdata.append(line) - if pdata: - yield Packet(pdata) + """Reads a pcap file with TCPDump, yielding Packet objects.""" + pdata = [] + for line in output.splitlines(): + if line[0] not in string.whitespace and pdata: + yield Packet(pdata) + pdata = [] + pdata.append(line) + if pdata: + yield Packet(pdata) def main(): - class CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter): - def _format_usage(self, usage, actions, groups, prefix=None): - header =('TestCreator creates gopacket tests using a pcap file.\n\n' - 'Tests are written to standard out... they can then be \n' - 'copied into the file of your choice and modified as \n' - 'you see.\n\n') - return header + argparse.ArgumentDefaultsHelpFormatter._format_usage( - self, usage, actions, groups, prefix) - - parser = argparse.ArgumentParser(formatter_class=CustomHelpFormatter) - parser.add_argument('--link_type', default='Ethernet', help='the link type (default: %(default)s)') - parser.add_argument('--name', default='Packet%d', help='the layer type, must have "%d" inside it') - parser.add_argument('files', metavar='file.pcap', type=str, nargs='+', help='the files to process') - - args = parser.parse_args() - - for arg in args.files: - for path in glob.glob(arg): - for i, packet in enumerate(TcpdumpOutputToPackets(GetTcpdumpOutput(path))): - print '\n'.join(packet.Test( - args.name % i, args.link_type)) + class CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter): + + def _format_usage(self, usage, actions, groups, prefix=None): + header = ('TestCreator creates gopacket tests using a pcap file.\n\n' + 'Tests are written to standard out... they can then be \n' + 'copied into the file of your choice and modified as \n' + 'you see.\n\n') + return header + argparse.ArgumentDefaultsHelpFormatter._format_usage( + self, usage, actions, groups, prefix) + + parser = argparse.ArgumentParser(formatter_class=CustomHelpFormatter) + parser.add_argument('--link_type', default='Ethernet', help='the link type (default: %(default)s)') + parser.add_argument('--name', default='Packet%d', help='the layer type, must have "%d" inside it') + parser.add_argument('files', metavar='file.pcap', type=str, nargs='+', help='the files to process') + + args = parser.parse_args() + + for arg in args.files: + for path in glob.glob(arg): + for i, packet in enumerate(TcpdumpOutputToPackets(GetTcpdumpOutput(path))): + print('\n'.join(packet.Test(args.name % i, args.link_type))) if __name__ == '__main__': main() diff --git a/winlogbeat/tests/system/test_config.py b/winlogbeat/tests/system/test_config.py index 8eaa3354aa2..bd2804cb569 100644 --- a/winlogbeat/tests/system/test_config.py +++ b/winlogbeat/tests/system/test_config.py @@ -9,6 +9,7 @@ @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") class Test(BaseTest): + def test_valid_config(self): """ configtest - valid config diff --git a/winlogbeat/tests/system/test_eventlogging.py b/winlogbeat/tests/system/test_eventlogging.py index 3634e4bc39c..05ed927a7a2 100644 --- a/winlogbeat/tests/system/test_eventlogging.py +++ b/winlogbeat/tests/system/test_eventlogging.py @@ -13,6 +13,7 @@ @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") class Test(WriteReadTest): + @classmethod def setUpClass(self): self.api = "eventlogging" diff --git a/winlogbeat/tests/system/test_wineventlog.py b/winlogbeat/tests/system/test_wineventlog.py index 28e2f99f4de..25ad14ffccb 100644 --- a/winlogbeat/tests/system/test_wineventlog.py +++ b/winlogbeat/tests/system/test_wineventlog.py @@ -14,6 +14,7 @@ @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") class Test(WriteReadTest): + @classmethod def setUpClass(self): self.api = "wineventlog" @@ -189,15 +190,14 @@ def test_query_level_single(self): self.assertTrue(len(evts), 1) self.assertEqual(evts[0]["level"], "Warning") - def test_query_level_multiple(self): """ wineventlog - Query by level (error, warning) """ - self.write_event_log("success", level=win32evtlog.EVENTLOG_SUCCESS) # Level 0, Info - self.write_event_log("error", level=win32evtlog.EVENTLOG_ERROR_TYPE) # Level 2 - self.write_event_log("warning", level=win32evtlog.EVENTLOG_WARNING_TYPE) # Level 3 - self.write_event_log("information", level=win32evtlog.EVENTLOG_INFORMATION_TYPE) # Level 4 + self.write_event_log("success", level=win32evtlog.EVENTLOG_SUCCESS) # Level 0, Info + self.write_event_log("error", level=win32evtlog.EVENTLOG_ERROR_TYPE) # Level 2 + self.write_event_log("warning", level=win32evtlog.EVENTLOG_WARNING_TYPE) # Level 3 + self.write_event_log("information", level=win32evtlog.EVENTLOG_INFORMATION_TYPE) # Level 4 evts = self.read_events(config={ "event_logs": [ { diff --git a/winlogbeat/tests/system/winlogbeat.py b/winlogbeat/tests/system/winlogbeat.py index ed42a8c7639..e6f2a305357 100644 --- a/winlogbeat/tests/system/winlogbeat.py +++ b/winlogbeat/tests/system/winlogbeat.py @@ -12,11 +12,13 @@ class BaseTest(TestCase): + @classmethod def setUpClass(self): self.beat_name = "winlogbeat" super(BaseTest, self).setUpClass() + class WriteReadTest(BaseTest): providerName = "WinlogbeatTestPython" applicationName = "SystemTest" From 3366bdb62fe6c603119601bb17b115c8f99bd1e6 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 31 Jan 2017 16:37:13 +0100 Subject: [PATCH 38/78] Fix leaking go routine in docker stats fetching (#3492) Go routine on line 79 never stopped as queue was not closed. Fixes https://github.com/elastic/beats/issues/3489 --- CHANGELOG.asciidoc | 1 + metricbeat/module/docker/docker.go | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5f04ca4cffa..f54b760616f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -37,6 +37,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Kafka module case sensitive host name matching. {pull}3193[3193] - Fix interface conversion panic in couchbase module {pull}3272[3272] - Fix overwriting explicit empty config sections {issue}2918[2918] +- Fix go routine leak in docker module. {pull}3492[3492] *Packetbeat* diff --git a/metricbeat/module/docker/docker.go b/metricbeat/module/docker/docker.go index 67437eef1b8..79aa42988da 100644 --- a/metricbeat/module/docker/docker.go +++ b/metricbeat/module/docker/docker.go @@ -66,24 +66,26 @@ func FetchStats(client *docker.Client) ([]Stat, error) { var wg sync.WaitGroup - containersList := []Stat{} + containersList := make([]Stat, 0, len(containers)) queue := make(chan Stat, 1) wg.Add(len(containers)) for _, container := range containers { go func(container docker.APIContainers) { + defer wg.Done() queue <- exportContainerStats(client, &container) }(container) } go func() { - for container := range queue { - containersList = append(containersList, container) - wg.Done() - } + wg.Wait() + close(queue) }() - wg.Wait() + // This will break after the queue has been drained and queue is closed. + for container := range queue { + containersList = append(containersList, container) + } return containersList, err } From 74c6eb671d6617282c6909d41ee54cbc3b774dae Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 31 Jan 2017 16:42:35 +0100 Subject: [PATCH 39/78] Simplify building of docker containers test environment (#3449) This change will allow to change download path and stack version in a single place (`args.yml`) instead of having it to apply multiple times. This should simplify updating the stack to a new version. * Set default ENV to snapshot as this is the most used one for manual testing * This Change also allows to use args to set different environments * Update latest build to 5.1.2 --- testing/environments/Makefile | 2 +- testing/environments/README.md | 2 +- testing/environments/args.yml | 9 ++++++++ .../docker/elasticsearch/Dockerfile-snapshot | 13 +++++------ .../docker/kibana/Dockerfile-snapshot | 8 +++---- .../environments/docker/logstash/Dockerfile | 8 +++---- testing/environments/latest.yml | 8 +++---- testing/environments/snapshot.yml | 23 +++++++++++-------- 8 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 testing/environments/args.yml diff --git a/testing/environments/Makefile b/testing/environments/Makefile index 508e63b7045..71123a91bb7 100644 --- a/testing/environments/Makefile +++ b/testing/environments/Makefile @@ -1,4 +1,4 @@ -ENV?=latest.yml +ENV?=snapshot.yml BASE_COMMAND=docker-compose -f ${ENV} -f local.yml start: diff --git a/testing/environments/README.md b/testing/environments/README.md index e0653b24fb1..332e52bfe2a 100644 --- a/testing/environments/README.md +++ b/testing/environments/README.md @@ -45,7 +45,7 @@ It is useful to sometimes access the containers from a browser, especially for K http://docker-machine-ip:5601/ ``` -Often de default ip is `192.168.99.100`. +Often the default address is `localhost`. ## Cleanup diff --git a/testing/environments/args.yml b/testing/environments/args.yml new file mode 100644 index 00000000000..dcc4de97778 --- /dev/null +++ b/testing/environments/args.yml @@ -0,0 +1,9 @@ +# This file contains the build arguments which are shared between all elastic stack containers +# This allows to change the build id and download path in a single place. +version: '2' +services: + args: + build: + args: + DOWNLOAD_URL: https://snapshots.elastic.co/downloads + ELASTIC_VERSION: 6.0.0-alpha1-SNAPSHOT diff --git a/testing/environments/docker/elasticsearch/Dockerfile-snapshot b/testing/environments/docker/elasticsearch/Dockerfile-snapshot index 9149d491b65..bf49aa71572 100644 --- a/testing/environments/docker/elasticsearch/Dockerfile-snapshot +++ b/testing/environments/docker/elasticsearch/Dockerfile-snapshot @@ -3,9 +3,8 @@ FROM docker.elastic.co/elasticsearch/elasticsearch-alpine-base:latest MAINTAINER Elastic Docker Team ARG ELASTIC_VERSION -ARG ES_DOWNLOAD_URL +ARG DOWNLOAD_URL ARG ES_JAVA_OPTS -ARG XPACK=x-pack ENV PATH /usr/share/elasticsearch/bin:$PATH ENV JAVA_HOME /usr/lib/jvm/java-1.8-openjdk @@ -13,8 +12,8 @@ ENV JAVA_HOME /usr/lib/jvm/java-1.8-openjdk WORKDIR /usr/share/elasticsearch # Download/extract defined ES version. busybox tar can't strip leading dir. -RUN wget ${ES_DOWNLOAD_URL}/elasticsearch-${ELASTIC_VERSION}.tar.gz && \ - EXPECTED_SHA=$(wget -O - ${ES_DOWNLOAD_URL}/elasticsearch-${ELASTIC_VERSION}.tar.gz.sha1) && \ +RUN wget ${DOWNLOAD_URL}/elasticsearch/elasticsearch-${ELASTIC_VERSION}.tar.gz && \ + EXPECTED_SHA=$(wget -O - ${DOWNLOAD_URL}/elasticsearch/elasticsearch-${ELASTIC_VERSION}.tar.gz.sha1) && \ test $EXPECTED_SHA == $(sha1sum elasticsearch-${ELASTIC_VERSION}.tar.gz | awk '{print $1}') && \ tar zxf elasticsearch-${ELASTIC_VERSION}.tar.gz && \ chown -R elasticsearch:elasticsearch elasticsearch-${ELASTIC_VERSION} && \ @@ -30,10 +29,10 @@ RUN set -ex && for esdirs in config data logs; do \ USER elasticsearch # Install xpack -#RUN eval ${ES_JAVA_OPTS:-} elasticsearch-plugin install --batch ${XPACK} +#RUN eval ${ES_JAVA_OPTS:-} elasticsearch-plugin install --batch ${DOWNLOAD_URL}/packs/x-pack/x-pack-${ELASTIC_VERSION}.zip -RUN elasticsearch-plugin install --batch https://snapshots.elastic.co/downloads/elasticsearch-plugins/ingest-user-agent/ingest-user-agent-6.0.0-alpha1-SNAPSHOT.zip -RUN elasticsearch-plugin install --batch https://snapshots.elastic.co/downloads/elasticsearch-plugins/ingest-geoip/ingest-geoip-6.0.0-alpha1-SNAPSHOT.zip +RUN elasticsearch-plugin install --batch ${DOWNLOAD_URL}/elasticsearch-plugins/ingest-user-agent/ingest-user-agent-${ELASTIC_VERSION}.zip +RUN elasticsearch-plugin install --batch ${DOWNLOAD_URL}/elasticsearch-plugins/ingest-geoip/ingest-geoip-${ELASTIC_VERSION}.zip COPY config/elasticsearch.yml config/ COPY config/log4j2.properties config/ diff --git a/testing/environments/docker/kibana/Dockerfile-snapshot b/testing/environments/docker/kibana/Dockerfile-snapshot index 143221a8a5d..82d8fbdda12 100644 --- a/testing/environments/docker/kibana/Dockerfile-snapshot +++ b/testing/environments/docker/kibana/Dockerfile-snapshot @@ -2,14 +2,14 @@ FROM docker.elastic.co/kibana/kibana-ubuntu-base:latest MAINTAINER Elastic Docker Team -ARG KIBANA_DOWNLOAD_URL=https://snapshots.elastic.co/downloads/kibana/kibana-6.0.0-alpha1-SNAPSHOT-linux-x86_64.tar.gz -ARG X_PACK_URL +ARG DOWNLOAD_URL +ARG ELASTIC_VERSION EXPOSE 5601 WORKDIR /usr/share/kibana -RUN curl -Ls ${KIBANA_DOWNLOAD_URL} | tar --strip-components=1 -zxf - && \ - #bin/kibana-plugin install ${X_PACK_URL} && \ +RUN curl -Ls ${DOWNLOAD_URL}/kibana/kibana-${ELASTIC_VERSION}-linux-x86_64.tar.gz | tar --strip-components=1 -zxf - && \ + #bin/kibana-plugin install ${DOWNLOAD_URL}/kibana-plugins/x-pack/x-pack-${ELASTIC_VERSION}.zip} && \ ln -s /usr/share/kibana /opt/kibana # Set some Kibana configuration defaults. diff --git a/testing/environments/docker/logstash/Dockerfile b/testing/environments/docker/logstash/Dockerfile index a4727e7ee18..37bc972de92 100644 --- a/testing/environments/docker/logstash/Dockerfile +++ b/testing/environments/docker/logstash/Dockerfile @@ -1,10 +1,10 @@ FROM java:8-jre -ARG LS_DOWNLOAD_URL -ARG LS_VERSION +ARG DOWNLOAD_URL +ARG ELASTIC_VERSION -ENV URL ${LS_DOWNLOAD_URL}/logstash-${LS_VERSION}.tar.gz -ENV PATH $PATH:/opt/logstash-${LS_VERSION}/bin +ENV URL ${DOWNLOAD_URL}/logstash/logstash-${ELASTIC_VERSION}.tar.gz +ENV PATH $PATH:/opt/logstash-${ELASTIC_VERSION}/bin # Cache variable can be set during building to invalidate the build cache with `--build-arg CACHE=$(date +%s) .` ARG CACHE=1 diff --git a/testing/environments/latest.yml b/testing/environments/latest.yml index dde1456939a..e06d01fc96d 100644 --- a/testing/environments/latest.yml +++ b/testing/environments/latest.yml @@ -4,7 +4,7 @@ version: '2' services: elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:5.1.1 + image: docker.elastic.co/elasticsearch/elasticsearch:5.1.2 environment: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - "network.host=" @@ -17,10 +17,10 @@ services: context: docker/logstash dockerfile: Dockerfile args: - LS_VERSION: 5.1.1 - LS_DOWNLOAD_URL: https://artifacts.elastic.co/downloads/logstash + ELASTIC_VERSION: 5.1.2 + DOWNLOAD_URL: https://artifacts.elastic.co/downloads environment: - ES_HOST=elasticsearch kibana: - image: docker.elastic.co/kibana/kibana:5.1.1 + image: docker.elastic.co/kibana/kibana:5.1.2 diff --git a/testing/environments/snapshot.yml b/testing/environments/snapshot.yml index 67fe7c8a6db..de5392116db 100644 --- a/testing/environments/snapshot.yml +++ b/testing/environments/snapshot.yml @@ -1,15 +1,16 @@ # This should test the environment with the latest snapshots # This is based on base.yml + + version: '2' services: elasticsearch: + extends: + file: ./args.yml + service: args build: context: ./docker/elasticsearch dockerfile: Dockerfile-snapshot - args: - ELASTIC_VERSION: 6.0.0-alpha1-SNAPSHOT - ES_DOWNLOAD_URL: https://snapshots.elastic.co/downloads/elasticsearch - #XPACK: http://snapshots.elastic.co/downloads/packs/x-pack/x-pack-6.0.0-alpha1-SNAPSHOT.zip environment: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - "network.host=" @@ -17,19 +18,21 @@ services: - "http.host=0.0.0.0" logstash: + extends: + file: ./args.yml + service: args build: context: ./docker/logstash dockerfile: Dockerfile - args: - LS_VERSION: 6.0.0-alpha1-SNAPSHOT - LS_DOWNLOAD_URL: https://snapshots.elastic.co/downloads/logstash environment: - ES_HOST=elasticsearch kibana: + extends: + file: ./args.yml + service: args build: context: ./docker/kibana dockerfile: Dockerfile-snapshot - args: [ ] - #KIBANA_DOWNLOAD_URL: https://snapshots.elastic.co/downloads/kibana/kibana-6.0.0-alpha1-SNAPSHOT-linux-x86_64.tar.gz - #X_PACK_URL: http://snapshots.elastic.co/downloads/kibana-plugins/x-pack/x-pack-6.0.0-alpha1-SNAPSHOT.zip + + From 034440b7dde22f4cc73ccc6ccaf4780c759ab1b2 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Tue, 31 Jan 2017 15:15:32 -0500 Subject: [PATCH 40/78] Fix protocol plugin import statement in Packetbeat (#3500) No protocols were registered because the import statement was for Metricbeat modules and not Packetbeat protocols. --- packetbeat/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packetbeat/main.go b/packetbeat/main.go index 9b63cba2546..e402c13ca5b 100644 --- a/packetbeat/main.go +++ b/packetbeat/main.go @@ -4,8 +4,10 @@ import ( "os" "github.com/elastic/beats/libbeat/beat" - _ "github.com/elastic/beats/metricbeat/include" "github.com/elastic/beats/packetbeat/beater" + + // import protocol modules + _ "github.com/elastic/beats/packetbeat/include" ) var Name = "packetbeat" From 6b2684cf746daa0393751e2698e0444156befa71 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 1 Feb 2017 10:17:01 +0100 Subject: [PATCH 41/78] Add Heartbeat to README.md (#3508) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a5a8f9091f7..4c1b3ef0256 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ framework for creating Beats, and all the officially supported Beats: Beat | Description --- | --- [Filebeat](https://github.com/elastic/beats/tree/master/filebeat) | Tails and ships log files +[Heartbeat](https://github.com/elastic/beats/tree/master/heartbeat) | Ping remote services for availability [Metricbeat](https://github.com/elastic/beats/tree/master/metricbeat) | Fetches sets of metrics from the operating system and services [Packetbeat](https://github.com/elastic/beats/tree/master/packetbeat) | Monitors the network and applications by sniffing packets [Winlogbeat](https://github.com/elastic/beats/tree/master/winlogbeat) | Fetches and ships Windows Event logs @@ -38,6 +39,7 @@ on the [elastic.co site](https://www.elastic.co/guide/): * [Beats platform](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) * [Filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/index.html) +* [Heartbeat](https://www.elastic.co/guide/en/beats/heartbeat/current/index.html) * [Metricbeat](https://www.elastic.co/guide/en/beats/metricbeat/current/index.html) * [Packetbeat](https://www.elastic.co/guide/en/beats/packetbeat/current/index.html) * [Winlogbeat](https://www.elastic.co/guide/en/beats/winlogbeat/current/index.html) From 0e6fbb011cc918a32671c64487557a1b37db92e4 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Wed, 1 Feb 2017 01:18:31 -0800 Subject: [PATCH 42/78] Add edits for load and socket metricset docs (#3495) * Add edits for load and socket metricset docs * Run make update to update fields.asciidoc --- metricbeat/docs/fields.asciidoc | 2 +- .../module/system/load/_meta/docs.asciidoc | 2 +- .../module/system/socket/_meta/docs.asciidoc | 38 ++++++++++--------- .../module/system/socket/_meta/fields.yml | 2 +- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 7eb7d5a5bb3..6041737cebc 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -6811,7 +6811,7 @@ type: ip example: 192.0.2.1 or 2001:0DB8:ABED:8536::1 -Remote IP address. This can be an IPv4 or IPv6 address +Remote IP address. This can be an IPv4 or IPv6 address. [float] diff --git a/metricbeat/module/system/load/_meta/docs.asciidoc b/metricbeat/module/system/load/_meta/docs.asciidoc index d043fd5f86a..5dbebca6aca 100644 --- a/metricbeat/module/system/load/_meta/docs.asciidoc +++ b/metricbeat/module/system/load/_meta/docs.asciidoc @@ -1,4 +1,4 @@ -=== system load MetricSet +=== System Load Metricset The System `load` metricset provides load statistics. diff --git a/metricbeat/module/system/socket/_meta/docs.asciidoc b/metricbeat/module/system/socket/_meta/docs.asciidoc index 4b593d8b3fc..86868b3fc4e 100644 --- a/metricbeat/module/system/socket/_meta/docs.asciidoc +++ b/metricbeat/module/system/socket/_meta/docs.asciidoc @@ -1,37 +1,39 @@ -=== system socket MetricSet +=== System Socket Metricset -WARNING: This functionality is in beta and is subject to change. It should be -deployed in production at your own risk. +beta[] This metricset is available on Linux only and requires kernel 2.6.14 or newer. -The system socket metricset reports an event for each new TCP socket that it -sees. It does this by polling the kernel to get a dump of all sockets. So using -a short polling interval with this metricset is important to not miss short -lived connections. You can configure a period specifically for this module by -declaring it separately from other system metricsets. +The system `socket` metricset reports an event for each new TCP socket that it +sees. It does this by polling the kernel periodically to get a dump of all +sockets. You set the polling interval by configuring the `period` option. +Specifying a short polling interval with this metricset is important to avoid +missing short-lived connections. For example: [source,yaml] --- metricbeat.modules: - module: system metricsets: [cpu, memory] -- module: system - metricsets: [socket] +- module: system + metricsets: [socket] <1> period: 1s --- +<1> You can configure the `socket` metricset separately to specify a different +`period` value than the other metricsets. + The metricset reports the process that has the socket open. In order to provide -this information Metricbeat must be running as root. It needs to be root to read -the file descriptor information of other processes. +this information, Metricbeat must be running as root. Root access is also +required to read the file descriptor information of other processes. -A reverse lookup can be performed by the metricset on the remote IP and the -returned hostname will be added to the event and cached. The is disabled by -default and can be enabled through the configuration. If a hostname is found -then the eTLD+1 (effective top-level domain plus one level) value will also be -added to the event. +You can configure the metricset to perform a reverse lookup on the remote IP, +and the returned hostname will be added to the event and cached. If a hostname +is found, then the eTLD+1 (effective top-level domain plus one level) value will +also be added to the event. Reverse lookups are disabled by default. -The full configuration for the metricset is shown below with its defaults. +The following example shows the full configuration for the metricset along with +the defaults. [source,yaml] --- diff --git a/metricbeat/module/system/socket/_meta/fields.yml b/metricbeat/module/system/socket/_meta/fields.yml index f6e11f67bf4..d3c5a476756 100644 --- a/metricbeat/module/system/socket/_meta/fields.yml +++ b/metricbeat/module/system/socket/_meta/fields.yml @@ -32,7 +32,7 @@ type: ip example: 192.0.2.1 or 2001:0DB8:ABED:8536::1 description: > - Remote IP address. This can be an IPv4 or IPv6 address + Remote IP address. This can be an IPv4 or IPv6 address. - name: remote.port type: long From d8702d7fffd3365cab4c848b945d414ac0883c77 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Wed, 1 Feb 2017 10:26:58 +0100 Subject: [PATCH 43/78] Fix .gitignore for beat.full.yml (#3505) --- metricbeat/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metricbeat/.gitignore b/metricbeat/.gitignore index 91c5cac653c..9ed47e11e1d 100644 --- a/metricbeat/.gitignore +++ b/metricbeat/.gitignore @@ -1,7 +1,7 @@ build _meta/kibana _meta/beat.yml -_meta.beat.full.yml +_meta/beat.full.yml /metricbeat /metricbeat.test From 0c0cbba0fbe2597406fa6797bb90573c743c791d Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Wed, 1 Feb 2017 13:27:11 +0100 Subject: [PATCH 44/78] Monitoring enhancements: (#3478) - remove NewInt/Float/String from registry: replace NewInt/Float/String and (*Registry).NewX by one NewX accepting the target registry by default. if nil is choosen, we default to the Default registry - Introduce 'Report' flag to metrics to mark some basic metrics - Add new adapter filters: ReportIf and ReportNames - apply variable options on registry or variable node. A variable 'inherits' the options from it's registry on Add => using `monitoring.Report` on registry will set `report` on all variables added to the sub-registry - move flattened snapshot support to monitoring/snapshot.go - Replace global http handler for httpprof with multiplexer configuring a only selected set of HTTP handlers (do not expose 'unknown' handlers from third party packages by accident) - custom /debug/vars http handler support expvar and libbeat/monitoring (output fully compatible to old expvar output) --- libbeat/logp/metrics.go | 111 ++----------- libbeat/logp/metrics_test.go | 6 +- libbeat/monitoring/adapter/filters.go | 29 +++- libbeat/monitoring/adapter/go-metrics.go | 3 +- libbeat/monitoring/adapter/go-metrics_test.go | 2 +- libbeat/monitoring/metrics.go | 80 +++++++-- libbeat/monitoring/monitoring.go | 20 ++- libbeat/monitoring/opts.go | 19 +++ libbeat/monitoring/registry.go | 154 +++++++----------- libbeat/monitoring/registry_test.go | 16 +- libbeat/monitoring/snapshot.go | 91 +++++++++++ libbeat/monitoring/visitor.go | 5 - libbeat/monitoring/visitor_expvar_test.go | 2 +- libbeat/service/service.go | 44 ++++- 14 files changed, 347 insertions(+), 235 deletions(-) create mode 100644 libbeat/monitoring/snapshot.go diff --git a/libbeat/logp/metrics.go b/libbeat/logp/metrics.go index d4b5f7b0f57..1476640d08e 100644 --- a/libbeat/logp/metrics.go +++ b/libbeat/logp/metrics.go @@ -4,24 +4,11 @@ import ( "bytes" "fmt" "sort" - "strings" "time" "github.com/elastic/beats/libbeat/monitoring" ) -type snapshotVisitor struct { - snapshot snapshot - level []string -} - -type snapshot struct { - bools map[string]bool - ints map[string]int64 - floats map[string]float64 - strings map[string]string -} - // logMetrics logs at Info level the integer expvars that have changed in the // last interval. For each expvar, the delta from the beginning of the interval // is logged. @@ -37,7 +24,7 @@ func logMetrics(metricsCfg *LoggingMetricsConfig) { ticker := time.NewTicker(*metricsCfg.Period) - prevVals := makeSnapshot() + prevVals := monitoring.MakeFlatSnapshot() for range ticker.C { snapshot := snapshotMetrics() delta := snapshotDelta(prevVals, snapshot) @@ -59,109 +46,39 @@ func LogTotalExpvars(cfg *Logging) { return } - metrics := formatMetrics(snapshotDelta(makeSnapshot(), snapshotMetrics())) + zero := monitoring.MakeFlatSnapshot() + metrics := formatMetrics(snapshotDelta(zero, snapshotMetrics())) Info("Total non-zero values: %s", metrics) Info("Uptime: %s", time.Now().Sub(startTime)) } -func snapshotMetrics() snapshot { - vs := newSnapshotVisitor() - monitoring.Default.Visit(vs) - monitoring.VisitExpvars(vs) - return vs.snapshot -} - -func newSnapshotVisitor() *snapshotVisitor { - return &snapshotVisitor{snapshot: makeSnapshot()} -} - -func makeSnapshot() snapshot { - return snapshot{ - bools: map[string]bool{}, - ints: map[string]int64{}, - floats: map[string]float64{}, - strings: map[string]string{}, - } -} - -func (vs *snapshotVisitor) OnRegistryStart() error { - return nil -} - -func (vs *snapshotVisitor) OnRegistryFinished() error { - if len(vs.level) > 0 { - vs.dropName() - } - return nil -} - -func (vs *snapshotVisitor) OnKey(name string) error { - vs.level = append(vs.level, name) - return nil -} - -func (vs *snapshotVisitor) OnKeyNext() error { return nil } - -func (vs *snapshotVisitor) getName() string { - defer vs.dropName() - if len(vs.level) == 1 { - return vs.level[0] - } - return strings.Join(vs.level, ".") -} - -func (vs *snapshotVisitor) dropName() { - vs.level = vs.level[:len(vs.level)-1] -} - -func (vs *snapshotVisitor) OnString(s string) error { - vs.snapshot.strings[vs.getName()] = s - return nil -} - -func (vs *snapshotVisitor) OnBool(b bool) error { - vs.snapshot.bools[vs.getName()] = b - return nil -} - -func (vs *snapshotVisitor) OnNil() error { - vs.snapshot.strings[vs.getName()] = "" - return nil -} - -func (vs *snapshotVisitor) OnInt(i int64) error { - vs.snapshot.ints[vs.getName()] = i - return nil -} - -func (vs *snapshotVisitor) OnFloat(f float64) error { - vs.snapshot.floats[vs.getName()] = f - return nil +func snapshotMetrics() monitoring.FlatSnapshot { + return monitoring.CollectFlatSnapshot(monitoring.Default, true) } -func snapshotDelta(prev, cur snapshot) map[string]interface{} { +func snapshotDelta(prev, cur monitoring.FlatSnapshot) map[string]interface{} { out := map[string]interface{}{} - for k, b := range cur.bools { - if p, ok := prev.bools[k]; !ok || p != b { + for k, b := range cur.Bools { + if p, ok := prev.Bools[k]; !ok || p != b { out[k] = b } } - for k, s := range cur.strings { - if p, ok := prev.strings[k]; !ok || p != s { + for k, s := range cur.Strings { + if p, ok := prev.Strings[k]; !ok || p != s { out[k] = s } } - for k, i := range cur.ints { - if p := prev.ints[k]; p != i { + for k, i := range cur.Ints { + if p := prev.Ints[k]; p != i { out[k] = i - p } } - for k, f := range cur.floats { - if p := prev.floats[k]; p != f { + for k, f := range cur.Floats { + if p := prev.Floats[k]; p != f { out[k] = f - p } } diff --git a/libbeat/logp/metrics_test.go b/libbeat/logp/metrics_test.go index d18dfdab586..fe5e87faa9b 100644 --- a/libbeat/logp/metrics_test.go +++ b/libbeat/logp/metrics_test.go @@ -14,7 +14,7 @@ func TestSnapshotExpvars(t *testing.T) { test.Add(42) vals := snapshotMetrics() - assert.Equal(t, vals.ints["test"], int64(42)) + assert.Equal(t, vals.Ints["test"], int64(42)) } func TestSnapshotExpvarsMap(t *testing.T) { @@ -27,8 +27,8 @@ func TestSnapshotExpvarsMap(t *testing.T) { vals := snapshotMetrics() - assert.Equal(t, vals.ints["testMap.hello"], int64(42)) - assert.Equal(t, vals.ints["testMap.map2.test"], int64(5)) + assert.Equal(t, vals.Ints["testMap.hello"], int64(42)) + assert.Equal(t, vals.Ints["testMap.map2.test"], int64(5)) } func TestBuildMetricsOutput(t *testing.T) { diff --git a/libbeat/monitoring/adapter/filters.go b/libbeat/monitoring/adapter/filters.go index b64d54ebc58..7ec431c647e 100644 --- a/libbeat/monitoring/adapter/filters.go +++ b/libbeat/monitoring/adapter/filters.go @@ -3,6 +3,7 @@ package adapter import ( "strings" + "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/monitoring" ) @@ -26,6 +27,7 @@ type varFilter func(state) state type state struct { kind kind action action + mode monitoring.Mode reg *monitoring.Registry name string metric interface{} @@ -90,16 +92,27 @@ func WhitelistIf(pred func(string) bool) MetricFilter { return ApplyIf(pred, Accept) } +// NameIn checks a metrics name matching any of the set names +func NameIn(names ...string) func(string) bool { + return common.MakeStringSet(names...).Has +} + // Whitelist sets a list of metric names to be accepted. func Whitelist(names ...string) MetricFilter { - return WhitelistIf(func(name string) bool { - for _, n := range names { - if name == n { - return true - } - } - return false - }) + return WhitelistIf(NameIn(names...)) +} + +// ReportIf sets variable report mode for all metrics satisfying the predicate. +func ReportIf(pred func(string) bool) MetricFilter { + return ApplyIf(pred, withVarFilter(func(st state) state { + st.mode = monitoring.Reported + return st + })) +} + +// ReportNames enables reporting for all metrics matching any of the given names. +func ReportNames(names ...string) MetricFilter { + return ReportIf(NameIn(names...)) } // ModifyName changes a metric its name using the provided diff --git a/libbeat/monitoring/adapter/go-metrics.go b/libbeat/monitoring/adapter/go-metrics.go index bd3f7e1030b..2d0b94188ac 100644 --- a/libbeat/monitoring/adapter/go-metrics.go +++ b/libbeat/monitoring/adapter/go-metrics.go @@ -127,7 +127,7 @@ func (r *GoMetricsRegistry) doRegister(name string, metric interface{}) interfac if st.action == actAccept { w, ok := goMetricsWrap(st.metric) if ok { - r.reg.Add(st.name, w) + r.reg.Add(st.name, w, st.mode) } } @@ -171,6 +171,7 @@ func (r *GoMetricsRegistry) stateWith(k kind, name string, metric interface{}) s action: actIgnore, reg: r.reg, name: name, + mode: monitoring.Full, metric: metric, }) } diff --git a/libbeat/monitoring/adapter/go-metrics_test.go b/libbeat/monitoring/adapter/go-metrics_test.go index b46086c84b1..dd0eeb6e992 100644 --- a/libbeat/monitoring/adapter/go-metrics_test.go +++ b/libbeat/monitoring/adapter/go-metrics_test.go @@ -80,7 +80,7 @@ func TestGoMetricsAdapter(t *testing.T) { t.Errorf("metric %v should not have been reported by each", name) } }) - monReg.Do(func(name string, v interface{}) error { + monReg.Do(monitoring.Full, func(name string, v interface{}) error { if !strings.HasPrefix(name, "test.mon") { t.Errorf("metric %v should not have been reported by each", name) } diff --git a/libbeat/monitoring/metrics.go b/libbeat/monitoring/metrics.go index 58e65a4754e..2d7adf142ee 100644 --- a/libbeat/monitoring/metrics.go +++ b/libbeat/monitoring/metrics.go @@ -1,7 +1,10 @@ package monitoring import ( + "encoding/json" + "expvar" "math" + "strconv" "sync" "sync/atomic" ) @@ -12,30 +15,54 @@ type makeExpvar func() string // Int is a 64 bit integer variable satisfying the Var interface. type Int struct{ i int64 } -// NewInt registers a new global integer metrics. -func NewInt(name string) *Int { - return Default.NewInt(name) +// NewInt creates and registers a new integer variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func NewInt(r *Registry, name string, opts ...Option) *Int { + if r == nil { + r = Default + } + + v := &Int{} + addVar(r, name, varOpts(r.opts, opts), v, makeExpvar(func() string { + return strconv.FormatInt(v.Get(), 10) + })) + return v } -func (v *Int) Visit(vs Visitor) error { return vs.OnInt(v.Get()) } func (v *Int) Get() int64 { return atomic.LoadInt64(&v.i) } func (v *Int) Set(value int64) { atomic.StoreInt64(&v.i, value) } func (v *Int) Add(delta int64) { atomic.AddInt64(&v.i, delta) } func (v *Int) Inc() { atomic.AddInt64(&v.i, 1) } func (v *Int) Dec() { atomic.AddInt64(&v.i, -1) } +func (v *Int) Visit(vs Visitor) error { return vs.OnInt(v.Get()) } // Float is a 64 bit float variable satisfying the Var interface. type Float struct{ f uint64 } -// NewFloat registers a new global floating point metric. -func NewFloat(name string) *Float { - return Default.NewFloat(name) +// NewFloat creates and registers a new float variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func NewFloat(r *Registry, name string, opts ...Option) *Float { + if r == nil { + r = Default + } + + v := &Float{} + addVar(r, name, varOpts(r.opts, opts), v, makeExpvar(func() string { + return strconv.FormatFloat(v.Get(), 'g', -1, 64) + })) + return v } -func (v *Float) Visit(vs Visitor) error { return vs.OnFloat(v.Get()) } func (v *Float) Get() float64 { return math.Float64frombits(atomic.LoadUint64(&v.f)) } func (v *Float) Set(value float64) { atomic.StoreUint64(&v.f, math.Float64bits(value)) } func (v *Float) Sub(delta float64) { v.Add(-delta) } +func (v *Float) Visit(vs Visitor) error { return vs.OnFloat(v.Get()) } func (v *Float) Add(delta float64) { for { @@ -53,12 +80,27 @@ type String struct { s string } -// NewString registers a new global string metric. -func NewString(name string) *String { - return Default.NewString(name) +// NewString creates and registers a new string variable. +// +// Note: If the registry is configured to publish variables to expvar, the +// variable will be available via expvars package as well, but can not be removed +// anymore. +func NewString(r *Registry, name string, opts ...Option) *String { + if r == nil { + r = Default + } + + v := &String{} + addVar(r, name, varOpts(r.opts, opts), v, makeExpvar(func() string { + b, _ := json.Marshal(v.Get()) + return string(b) + })) + return v } -func (v *String) Visit(vs Visitor) error { return vs.OnString(v.Get()) } +func (v *String) Visit(vs Visitor) error { + return vs.OnString(v.Get()) +} func (v *String) Get() string { v.mu.RLock() @@ -81,3 +123,17 @@ func (v *String) Fail(err error) { } func (m makeExpvar) String() string { return m() } + +func addVar(r *Registry, name string, opts options, v Var, ev expvar.Var) { + r.Add(name, v, opts.mode) + if opts.publishExpvar { + expvar.Publish(fullName(r, name), ev) + } +} + +func fullName(r *Registry, name string) string { + if r.name == "" { + return name + } + return r.name + "." + name +} diff --git a/libbeat/monitoring/monitoring.go b/libbeat/monitoring/monitoring.go index 8735526dd82..2ba41bc9faf 100644 --- a/libbeat/monitoring/monitoring.go +++ b/libbeat/monitoring/monitoring.go @@ -2,21 +2,35 @@ package monitoring import "errors" +type Mode uint8 + +const ( + // Reported mode, is lowest report level with most basic metrics only + Reported Mode = iota + + // Full reports all metrics + Full +) + // Default is the global default metrics registry provided by the monitoring package. var Default = NewRegistry() var errNotFound = errors.New("Name unknown") var errInvalidName = errors.New("Name does not point to a valid variable") +func VisitMode(mode Mode, vs Visitor) error { + return Default.VisitMode(mode, vs) +} + func Visit(vs Visitor) error { return Default.Visit(vs) } -func Do(f func(string, interface{}) error) error { - return Default.Do(f) +func Do(mode Mode, f func(string, interface{}) error) error { + return Default.Do(mode, f) } -func Get(name string) interface{} { +func Get(name string) Var { return Default.Get(name) } diff --git a/libbeat/monitoring/opts.go b/libbeat/monitoring/opts.go index bae2b02cd58..e9dfab549d2 100644 --- a/libbeat/monitoring/opts.go +++ b/libbeat/monitoring/opts.go @@ -5,10 +5,12 @@ type Option func(options) options type options struct { publishExpvar bool + mode Mode } var defaultOptions = options{ publishExpvar: false, + mode: Full, } // PublishExpvar enables publishing all registered variables via expvar interface. @@ -24,6 +26,23 @@ func IgnorePublishExpvar(o options) options { return o } +func Report(o options) options { + o.mode = Reported + return o +} + +func varOpts(regOpts *options, opts []Option) options { + O := defaultOptions + if regOpts != nil { + O = *regOpts + } + + for _, opt := range opts { + O = opt(O) + } + return O +} + func applyOpts(in *options, opts []Option) *options { if len(opts) == 0 { return ensureOptions(in) diff --git a/libbeat/monitoring/registry.go b/libbeat/monitoring/registry.go index cb45ccf8481..6ae17314bc0 100644 --- a/libbeat/monitoring/registry.go +++ b/libbeat/monitoring/registry.go @@ -1,11 +1,8 @@ package monitoring import ( - "encoding/json" "errors" - "expvar" "fmt" - "strconv" "strings" "sync" ) @@ -17,11 +14,16 @@ type Registry struct { mu sync.RWMutex name string - entries map[string]Var + entries map[string]entry opts *options } +type entry struct { + Var + Mode +} + // Var interface required for every metric to implement. type Var interface { Visit(Visitor) error @@ -31,18 +33,26 @@ type Var interface { func NewRegistry(opts ...Option) *Registry { return &Registry{ opts: applyOpts(nil, opts), - entries: map[string]Var{}, + entries: map[string]entry{}, } } -func (r *Registry) Do(f func(string, interface{}) error) error { - return r.Visit(NewKeyValueVisitor(f)) +func (r *Registry) Do(mode Mode, f func(string, interface{}) error) error { + return r.doVisit(mode, NewKeyValueVisitor(f)) +} + +func (r *Registry) VisitMode(mode Mode, vs Visitor) error { + return r.doVisit(mode, vs) } // Visit uses the Visitor interface to iterate the complete metrics hieararchie. // In case of the visitor reporting an error, Visit will return immediately, // reporting the very same error. func (r *Registry) Visit(vs Visitor) error { + return r.doVisit(Full, vs) +} + +func (r *Registry) doVisit(mode Mode, vs Visitor) error { if err := vs.OnRegistryStart(); err != nil { return err } @@ -52,19 +62,30 @@ func (r *Registry) Visit(vs Visitor) error { first := true for key, v := range r.entries { + var err error + + if v.Mode > mode { + continue + } + if first { first = false } else { - if err := vs.OnKeyNext(); err != nil { + if err = vs.OnKeyNext(); err != nil { return err } } - if err := vs.OnKey(key); err != nil { + if err = vs.OnKey(key); err != nil { return err } - if err := v.Visit(vs); err != nil { + if reg, ok := v.Var.(*Registry); ok { + err = reg.doVisit(mode, vs) + } else { + err = v.Var.Visit(vs) + } + if err != nil { return err } } @@ -75,73 +96,31 @@ func (r *Registry) Visit(vs Visitor) error { // NewRegistry creates and register a new registry func (r *Registry) NewRegistry(name string, opts ...Option) *Registry { v := &Registry{ - name: r.fullName(name), + name: fullName(r, name), opts: applyOpts(r.opts, opts), - entries: map[string]Var{}, + entries: map[string]entry{}, } - r.Add(name, v) - return v -} - -// NewInt creates and registers a new integer variable. -// -// Note: If the registry is configured to publish variables to expvar, the -// variable will be available via expvars package as well, but can not be removed -// anymore. -func (r *Registry) NewInt(name string) *Int { - v := &Int{} - r.Add(name, v) - r.publish(name, makeExpvar(func() string { - return strconv.FormatInt(v.Get(), 10) - })) - return v -} - -// NewFloat creates and registers a new float variable. -// -// Note: If the registry is configured to publish variables to expvar, the -// variable will be available via expvars package as well, but can not be removed -// anymore. -func (r *Registry) NewFloat(name string) *Float { - v := &Float{} - r.Add(name, v) - r.publish(name, makeExpvar(func() string { - return strconv.FormatFloat(v.Get(), 'g', -1, 64) - })) - return v -} - -// NewString creates and registers a new string variable. -// -// Note: If the registry is configured to publish variables to expvar, the -// variable will be available via expvars package as well, but can not be removed -// anymore. -func (r *Registry) NewString(name string) *String { - v := &String{} - r.Add(name, v) - r.publish(name, makeExpvar(func() string { - b, _ := json.Marshal(v.Get()) - return string(b) - })) + r.Add(name, v, v.opts.mode) return v } // Get tries to find a registered variable by name. -func (r *Registry) Get(name string) interface{} { +func (r *Registry) Get(name string) Var { v, err := r.find(name) if err != nil { return nil } - return v + return v.Var } // GetRegistry tries to find a sub-registry by name. func (r *Registry) GetRegistry(name string) *Registry { - v, err := r.find(name) + e, err := r.find(name) if err != nil { return nil } + v := e.Var if v == nil { return nil } @@ -168,32 +147,17 @@ func (r *Registry) Clear() error { return errors.New("Can not clear registry with metrics being exported via expvar") } - r.entries = map[string]Var{} + r.entries = map[string]entry{} return nil } -func (r *Registry) publish(name string, v expvar.Var) { - if !r.opts.publishExpvar { - return - } - - expvar.Publish(r.fullName(name), v) -} - -func (r *Registry) fullName(name string) string { - if r.name == "" { - return name - } - return r.name + "." + name -} - // Add adds a new variable to the registry. The method panics if the variables // name is already in use. -func (r *Registry) Add(name string, v Var) { - panicErr(r.addNames(strings.Split(name, "."), v)) +func (r *Registry) Add(name string, v Var, m Mode) { + panicErr(r.addNames(strings.Split(name, "."), v, m)) } -func (r *Registry) addNames(names []string, v Var) error { +func (r *Registry) addNames(names []string, v Var, m Mode) error { r.mu.Lock() defer r.mu.Unlock() @@ -203,37 +167,37 @@ func (r *Registry) addNames(names []string, v Var) error { return fmt.Errorf("name %v already used", name) } - r.entries[name] = v + r.entries[name] = entry{v, m} return nil } if tmp, found := r.entries[name]; found { - reg, ok := tmp.(*Registry) + reg, ok := tmp.Var.(*Registry) if !ok { return fmt.Errorf("name %v already used", name) } - return reg.addNames(names[1:], v) + return reg.addNames(names[1:], v, m) } sub := NewRegistry() sub.opts = r.opts - if err := sub.addNames(names[1:], v); err != nil { + if err := sub.addNames(names[1:], v, m); err != nil { return err } - r.entries[name] = sub + r.entries[name] = entry{sub, sub.opts.mode} return nil } -func (r *Registry) find(name string) (interface{}, error) { +func (r *Registry) find(name string) (entry, error) { return r.findNames(strings.Split(name, ".")) } -func (r *Registry) findNames(names []string) (interface{}, error) { +func (r *Registry) findNames(names []string) (entry, error) { switch len(names) { case 0: - return r, nil + return entry{r, r.opts.mode}, nil case 1: r.mu.RLock() defer r.mu.RUnlock() @@ -241,17 +205,17 @@ func (r *Registry) findNames(names []string) (interface{}, error) { } r.mu.RLock() - next := r.entries[names[0]] + next, exist := r.entries[names[0]] r.mu.RUnlock() - if next == nil { - return nil, errNotFound + if !exist { + return entry{}, errNotFound } - if reg, ok := next.(*Registry); ok { + if reg, ok := next.Var.(*Registry); ok { return reg.findNames(names[1:]) } - return nil, errInvalidName + return entry{}, errInvalidName } func (r *Registry) removeNames(names []string) { @@ -267,10 +231,14 @@ func (r *Registry) removeNames(names []string) { r.mu.Lock() defer r.mu.Unlock() - next := r.entries[names[0]] - sub, ok := next.(*Registry) + next, exists := r.entries[names[0]] // if name does not exist => don't remove anything + if !exists { + return + } + + sub, ok := next.Var.(*Registry) if ok { sub.removeNames(names[1:]) sub.mu.RLock() diff --git a/libbeat/monitoring/registry_test.go b/libbeat/monitoring/registry_test.go index d8222996956..ff70004ca40 100644 --- a/libbeat/monitoring/registry_test.go +++ b/libbeat/monitoring/registry_test.go @@ -46,11 +46,11 @@ func TestRegistryGet(t *testing.T) { name3 := nameSub2 + "." + name1 // register top-level and recursive metric - v1 := NewInt(name1) + v1 := NewInt(Default, name1, Report) sub1 := Default.NewRegistry(nameSub1) sub2 := Default.NewRegistry(nameSub2) - v2 := NewString(name2) - v3 := sub2.NewFloat(name1) + v2 := NewString(nil, name2, Report) + v3 := NewFloat(sub2, name1, Report) // get values v := Get(name1) @@ -86,11 +86,11 @@ func TestRegistryRemove(t *testing.T) { name3 := nameSub2 + "." + name1 // register top-level and recursive metric - NewInt(name1) + NewInt(Default, name1, Report) sub1 := Default.NewRegistry(nameSub1) sub2 := Default.NewRegistry(nameSub2) - NewInt(name2) - sub2.NewInt(name1) + NewInt(Default, name2, Report) + NewInt(sub2, name1, Report) // remove metrics: Remove(name1) @@ -113,12 +113,12 @@ func TestRegistryIter(t *testing.T) { } for name, v := range vars { - i := NewInt(name) + i := NewInt(Default, name, Report) i.Add(v) } collected := map[string]int64{} - err := Do(func(name string, v interface{}) error { + err := Do(Full, func(name string, v interface{}) error { collected[name] = v.(int64) return nil }) diff --git a/libbeat/monitoring/snapshot.go b/libbeat/monitoring/snapshot.go new file mode 100644 index 00000000000..b45de0a474c --- /dev/null +++ b/libbeat/monitoring/snapshot.go @@ -0,0 +1,91 @@ +package monitoring + +import "strings" + +// FlatSnapshot represents a flatten snapshot of all metrics. +// Names in the tree will be joined with `.` . +type FlatSnapshot struct { + Bools map[string]bool + Ints map[string]int64 + Floats map[string]float64 + Strings map[string]string +} + +type snapshotVisitor struct { + snapshot FlatSnapshot + level []string +} + +// CollectFlatSnapshot collects a flattened snapshot of +// a metrics tree start with the given registry. +func CollectFlatSnapshot(r *Registry, expvar bool) FlatSnapshot { + vs := newSnapshotVisitor() + r.Visit(vs) + if expvar { + VisitExpvars(vs) + } + return vs.snapshot +} + +func MakeFlatSnapshot() FlatSnapshot { + return FlatSnapshot{ + Bools: map[string]bool{}, + Ints: map[string]int64{}, + Floats: map[string]float64{}, + Strings: map[string]string{}, + } +} + +func newSnapshotVisitor() *snapshotVisitor { + return &snapshotVisitor{snapshot: MakeFlatSnapshot()} +} + +func (vs *snapshotVisitor) OnRegistryStart() error { + return nil +} + +func (vs *snapshotVisitor) OnRegistryFinished() error { + if len(vs.level) > 0 { + vs.dropName() + } + return nil +} + +func (vs *snapshotVisitor) OnKey(name string) error { + vs.level = append(vs.level, name) + return nil +} + +func (vs *snapshotVisitor) OnKeyNext() error { return nil } + +func (vs *snapshotVisitor) getName() string { + defer vs.dropName() + if len(vs.level) == 1 { + return vs.level[0] + } + return strings.Join(vs.level, ".") +} + +func (vs *snapshotVisitor) dropName() { + vs.level = vs.level[:len(vs.level)-1] +} + +func (vs *snapshotVisitor) OnString(s string) error { + vs.snapshot.Strings[vs.getName()] = s + return nil +} + +func (vs *snapshotVisitor) OnBool(b bool) error { + vs.snapshot.Bools[vs.getName()] = b + return nil +} + +func (vs *snapshotVisitor) OnInt(i int64) error { + vs.snapshot.Ints[vs.getName()] = i + return nil +} + +func (vs *snapshotVisitor) OnFloat(f float64) error { + vs.snapshot.Floats[vs.getName()] = f + return nil +} diff --git a/libbeat/monitoring/visitor.go b/libbeat/monitoring/visitor.go index 78fb9669ac9..dffa1bfda78 100644 --- a/libbeat/monitoring/visitor.go +++ b/libbeat/monitoring/visitor.go @@ -9,12 +9,7 @@ type Visitor interface { type ValueVisitor interface { OnString(s string) error OnBool(b bool) error - OnNil() error - - // int OnInt(i int64) error - - // float OnFloat(f float64) error } diff --git a/libbeat/monitoring/visitor_expvar_test.go b/libbeat/monitoring/visitor_expvar_test.go index b59ecd38c25..b11ef53cd95 100644 --- a/libbeat/monitoring/visitor_expvar_test.go +++ b/libbeat/monitoring/visitor_expvar_test.go @@ -19,7 +19,7 @@ func TestIterExpvarIgnoringMonitoringVars(t *testing.T) { reg := NewRegistry(PublishExpvar) for name, v := range vars { - i := reg.NewInt(name) + i := NewInt(reg, name, Report) i.Add(v) } diff --git a/libbeat/service/service.go b/libbeat/service/service.go index af585e8d9f5..3abfaa3f7c9 100644 --- a/libbeat/service/service.go +++ b/libbeat/service/service.go @@ -1,7 +1,9 @@ package service import ( + "expvar" "flag" + "fmt" "log" "os" "os/signal" @@ -11,10 +13,9 @@ import ( "syscall" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/monitoring" "net/http" - - // blank pprof import to load HTTP handler for debugging endpoint _ "net/http/pprof" ) @@ -72,11 +73,48 @@ func BeforeRun() { if *httpprof != "" { go func() { logp.Info("start pprof endpoint") - logp.Info("finished pprof endpoint: %v", http.ListenAndServe(*httpprof, nil)) + mux := http.NewServeMux() + + // register pprof handler + mux.HandleFunc("/debug/pprof/", func(w http.ResponseWriter, r *http.Request) { + http.DefaultServeMux.ServeHTTP(w, r) + }) + + // register metrics handler + mux.HandleFunc("/debug/vars", metricsHandler) + + endpoint := http.ListenAndServe(*httpprof, mux) + logp.Info("finished pprof endpoint: %v", endpoint) }() } } +// report expvar and all libbeat/monitoring metrics +func metricsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + first := true + report := func(key string, value interface{}) error { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + if str, ok := value.(string); ok { + fmt.Fprintf(w, "%q: %q", key, str) + } else { + fmt.Fprintf(w, "%q: %v", key, value) + } + return nil + } + + fmt.Fprintf(w, "{\n") + monitoring.Do(monitoring.Full, report) + expvar.Do(func(kv expvar.KeyValue) { + report(kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} + // Cleanup handles cleaning up the runtime and OS environments. This includes // tasks such as stopping the CPU profile if it is running. func Cleanup() { From 5494fcb237543de449792bf5e3b87c13eb169653 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Thu, 2 Feb 2017 00:27:27 +0100 Subject: [PATCH 45/78] Monitoring variable snapshoting improvements (#3510) - Pass Mode to monitoring.Var to filter within structured variables - do not return errors in Visitor interface - add helper functions ReportX --- libbeat/logp/metrics.go | 2 +- .../monitoring/adapter/go-metrics-wrapper.go | 56 ++++++++++++------- libbeat/monitoring/adapter/go-metrics_test.go | 3 +- libbeat/monitoring/metrics.go | 24 ++++---- libbeat/monitoring/monitoring.go | 12 ++-- libbeat/monitoring/registry.go | 48 ++++------------ libbeat/monitoring/registry_test.go | 4 +- libbeat/monitoring/snapshot.go | 28 +++------- libbeat/monitoring/visitor.go | 35 +++++++++--- libbeat/monitoring/visitor_expvar.go | 12 +--- libbeat/monitoring/visitor_kv.go | 40 ++++--------- libbeat/service/service.go | 3 +- 12 files changed, 114 insertions(+), 153 deletions(-) diff --git a/libbeat/logp/metrics.go b/libbeat/logp/metrics.go index 1476640d08e..6e2c8d0a909 100644 --- a/libbeat/logp/metrics.go +++ b/libbeat/logp/metrics.go @@ -53,7 +53,7 @@ func LogTotalExpvars(cfg *Logging) { } func snapshotMetrics() monitoring.FlatSnapshot { - return monitoring.CollectFlatSnapshot(monitoring.Default, true) + return monitoring.CollectFlatSnapshot(monitoring.Default, monitoring.Full, true) } func snapshotDelta(prev, cur monitoring.FlatSnapshot) map[string]interface{} { diff --git a/libbeat/monitoring/adapter/go-metrics-wrapper.go b/libbeat/monitoring/adapter/go-metrics-wrapper.go index f3c4f722d59..1c982f17b56 100644 --- a/libbeat/monitoring/adapter/go-metrics-wrapper.go +++ b/libbeat/monitoring/adapter/go-metrics-wrapper.go @@ -48,30 +48,44 @@ func goMetricsWrap(metric interface{}) (monitoring.Var, bool) { return nil, false } -func (w goMetricsCounter) wrapped() interface{} { return w.c } -func (w goMetricsCounter) Get() int64 { return w.c.Count() } -func (w goMetricsCounter) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } +func (w goMetricsCounter) wrapped() interface{} { return w.c } +func (w goMetricsCounter) Get() int64 { return w.c.Count() } +func (w goMetricsCounter) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnInt(w.Get()) +} -func (w goMetricsGauge) wrapped() interface{} { return w.g } -func (w goMetricsGauge) Get() int64 { return w.g.Value() } -func (w goMetricsGauge) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } +func (w goMetricsGauge) wrapped() interface{} { return w.g } +func (w goMetricsGauge) Get() int64 { return w.g.Value() } +func (w goMetricsGauge) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnInt(w.Get()) +} -func (w goMetricsGaugeFloat64) wrapped() interface{} { return w.g } -func (w goMetricsGaugeFloat64) Get() float64 { return w.g.Value() } -func (w goMetricsGaugeFloat64) Visit(vs monitoring.Visitor) error { return vs.OnFloat(w.Get()) } +func (w goMetricsGaugeFloat64) wrapped() interface{} { return w.g } +func (w goMetricsGaugeFloat64) Get() float64 { return w.g.Value() } +func (w goMetricsGaugeFloat64) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnFloat(w.Get()) +} -func (w goMetricsFuncGauge) wrapped() interface{} { return w.g } -func (w goMetricsFuncGauge) Get() int64 { return w.g.Value() } -func (w goMetricsFuncGauge) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } +func (w goMetricsFuncGauge) wrapped() interface{} { return w.g } +func (w goMetricsFuncGauge) Get() int64 { return w.g.Value() } +func (w goMetricsFuncGauge) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnInt(w.Get()) +} -func (w goMetricsFuncGaugeFloat) wrapped() interface{} { return w.g } -func (w goMetricsFuncGaugeFloat) Get() float64 { return w.g.Value() } -func (w goMetricsFuncGaugeFloat) Visit(vs monitoring.Visitor) error { return vs.OnFloat(w.Get()) } +func (w goMetricsFuncGaugeFloat) wrapped() interface{} { return w.g } +func (w goMetricsFuncGaugeFloat) Get() float64 { return w.g.Value() } +func (w goMetricsFuncGaugeFloat) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnFloat(w.Get()) +} -func (w goMetricsHistogram) wrapped() interface{} { return w.h } -func (w goMetricsHistogram) Get() int64 { return w.h.Sum() } -func (w goMetricsHistogram) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } +func (w goMetricsHistogram) wrapped() interface{} { return w.h } +func (w goMetricsHistogram) Get() int64 { return w.h.Sum() } +func (w goMetricsHistogram) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnInt(w.Get()) +} -func (w goMetricsMeter) wrapped() interface{} { return w.m } -func (w goMetricsMeter) Get() int64 { return w.m.Count() } -func (w goMetricsMeter) Visit(vs monitoring.Visitor) error { return vs.OnInt(w.Get()) } +func (w goMetricsMeter) wrapped() interface{} { return w.m } +func (w goMetricsMeter) Get() int64 { return w.m.Count() } +func (w goMetricsMeter) Visit(_ monitoring.Mode, vs monitoring.Visitor) { + vs.OnInt(w.Get()) +} diff --git a/libbeat/monitoring/adapter/go-metrics_test.go b/libbeat/monitoring/adapter/go-metrics_test.go index dd0eeb6e992..9fefa324ad1 100644 --- a/libbeat/monitoring/adapter/go-metrics_test.go +++ b/libbeat/monitoring/adapter/go-metrics_test.go @@ -80,10 +80,9 @@ func TestGoMetricsAdapter(t *testing.T) { t.Errorf("metric %v should not have been reported by each", name) } }) - monReg.Do(monitoring.Full, func(name string, v interface{}) error { + monReg.Do(monitoring.Full, func(name string, v interface{}) { if !strings.HasPrefix(name, "test.mon") { t.Errorf("metric %v should not have been reported by each", name) } - return nil }) } diff --git a/libbeat/monitoring/metrics.go b/libbeat/monitoring/metrics.go index 2d7adf142ee..0fe2c36b268 100644 --- a/libbeat/monitoring/metrics.go +++ b/libbeat/monitoring/metrics.go @@ -32,12 +32,12 @@ func NewInt(r *Registry, name string, opts ...Option) *Int { return v } -func (v *Int) Get() int64 { return atomic.LoadInt64(&v.i) } -func (v *Int) Set(value int64) { atomic.StoreInt64(&v.i, value) } -func (v *Int) Add(delta int64) { atomic.AddInt64(&v.i, delta) } -func (v *Int) Inc() { atomic.AddInt64(&v.i, 1) } -func (v *Int) Dec() { atomic.AddInt64(&v.i, -1) } -func (v *Int) Visit(vs Visitor) error { return vs.OnInt(v.Get()) } +func (v *Int) Get() int64 { return atomic.LoadInt64(&v.i) } +func (v *Int) Set(value int64) { atomic.StoreInt64(&v.i, value) } +func (v *Int) Add(delta int64) { atomic.AddInt64(&v.i, delta) } +func (v *Int) Inc() { atomic.AddInt64(&v.i, 1) } +func (v *Int) Dec() { atomic.AddInt64(&v.i, -1) } +func (v *Int) Visit(_ Mode, vs Visitor) { vs.OnInt(v.Get()) } // Float is a 64 bit float variable satisfying the Var interface. type Float struct{ f uint64 } @@ -59,10 +59,10 @@ func NewFloat(r *Registry, name string, opts ...Option) *Float { return v } -func (v *Float) Get() float64 { return math.Float64frombits(atomic.LoadUint64(&v.f)) } -func (v *Float) Set(value float64) { atomic.StoreUint64(&v.f, math.Float64bits(value)) } -func (v *Float) Sub(delta float64) { v.Add(-delta) } -func (v *Float) Visit(vs Visitor) error { return vs.OnFloat(v.Get()) } +func (v *Float) Get() float64 { return math.Float64frombits(atomic.LoadUint64(&v.f)) } +func (v *Float) Set(value float64) { atomic.StoreUint64(&v.f, math.Float64bits(value)) } +func (v *Float) Sub(delta float64) { v.Add(-delta) } +func (v *Float) Visit(_ Mode, vs Visitor) { vs.OnFloat(v.Get()) } func (v *Float) Add(delta float64) { for { @@ -98,8 +98,8 @@ func NewString(r *Registry, name string, opts ...Option) *String { return v } -func (v *String) Visit(vs Visitor) error { - return vs.OnString(v.Get()) +func (v *String) Visit(_ Mode, vs Visitor) { + vs.OnString(v.Get()) } func (v *String) Get() string { diff --git a/libbeat/monitoring/monitoring.go b/libbeat/monitoring/monitoring.go index 2ba41bc9faf..3b8295a862d 100644 --- a/libbeat/monitoring/monitoring.go +++ b/libbeat/monitoring/monitoring.go @@ -18,16 +18,16 @@ var Default = NewRegistry() var errNotFound = errors.New("Name unknown") var errInvalidName = errors.New("Name does not point to a valid variable") -func VisitMode(mode Mode, vs Visitor) error { - return Default.VisitMode(mode, vs) +func VisitMode(mode Mode, vs Visitor) { + Default.Visit(mode, vs) } -func Visit(vs Visitor) error { - return Default.Visit(vs) +func Visit(vs Visitor) { + Default.Visit(Full, vs) } -func Do(mode Mode, f func(string, interface{}) error) error { - return Default.Do(mode, f) +func Do(mode Mode, f func(string, interface{})) { + Default.Do(mode, f) } func Get(name string) Var { diff --git a/libbeat/monitoring/registry.go b/libbeat/monitoring/registry.go index 6ae17314bc0..f901a268d8a 100644 --- a/libbeat/monitoring/registry.go +++ b/libbeat/monitoring/registry.go @@ -26,7 +26,7 @@ type entry struct { // Var interface required for every metric to implement. type Var interface { - Visit(Visitor) error + Visit(Mode, Visitor) } // NewRegistry create a new empty unregistered registry @@ -37,60 +37,32 @@ func NewRegistry(opts ...Option) *Registry { } } -func (r *Registry) Do(mode Mode, f func(string, interface{}) error) error { - return r.doVisit(mode, NewKeyValueVisitor(f)) -} - -func (r *Registry) VisitMode(mode Mode, vs Visitor) error { - return r.doVisit(mode, vs) +func (r *Registry) Do(mode Mode, f func(string, interface{})) { + r.doVisit(mode, NewKeyValueVisitor(f)) } // Visit uses the Visitor interface to iterate the complete metrics hieararchie. // In case of the visitor reporting an error, Visit will return immediately, // reporting the very same error. -func (r *Registry) Visit(vs Visitor) error { - return r.doVisit(Full, vs) +func (r *Registry) Visit(mode Mode, vs Visitor) { + r.doVisit(mode, vs) } -func (r *Registry) doVisit(mode Mode, vs Visitor) error { - if err := vs.OnRegistryStart(); err != nil { - return err - } +func (r *Registry) doVisit(mode Mode, vs Visitor) { + vs.OnRegistryStart() + defer vs.OnRegistryFinished() r.mu.RLock() defer r.mu.RUnlock() - first := true for key, v := range r.entries { - var err error - if v.Mode > mode { continue } - if first { - first = false - } else { - if err = vs.OnKeyNext(); err != nil { - return err - } - } - - if err = vs.OnKey(key); err != nil { - return err - } - - if reg, ok := v.Var.(*Registry); ok { - err = reg.doVisit(mode, vs) - } else { - err = v.Var.Visit(vs) - } - if err != nil { - return err - } + vs.OnKey(key) + v.Var.Visit(mode, vs) } - - return vs.OnRegistryFinished() } // NewRegistry creates and register a new registry diff --git a/libbeat/monitoring/registry_test.go b/libbeat/monitoring/registry_test.go index ff70004ca40..d3970ce817c 100644 --- a/libbeat/monitoring/registry_test.go +++ b/libbeat/monitoring/registry_test.go @@ -118,11 +118,9 @@ func TestRegistryIter(t *testing.T) { } collected := map[string]int64{} - err := Do(Full, func(name string, v interface{}) error { + Do(Full, func(name string, v interface{}) { collected[name] = v.(int64) - return nil }) - assert.Nil(t, err) assert.Equal(t, vars, collected) } diff --git a/libbeat/monitoring/snapshot.go b/libbeat/monitoring/snapshot.go index b45de0a474c..4fd345bf484 100644 --- a/libbeat/monitoring/snapshot.go +++ b/libbeat/monitoring/snapshot.go @@ -18,9 +18,9 @@ type snapshotVisitor struct { // CollectFlatSnapshot collects a flattened snapshot of // a metrics tree start with the given registry. -func CollectFlatSnapshot(r *Registry, expvar bool) FlatSnapshot { +func CollectFlatSnapshot(r *Registry, mode Mode, expvar bool) FlatSnapshot { vs := newSnapshotVisitor() - r.Visit(vs) + r.Visit(mode, vs) if expvar { VisitExpvars(vs) } @@ -40,24 +40,18 @@ func newSnapshotVisitor() *snapshotVisitor { return &snapshotVisitor{snapshot: MakeFlatSnapshot()} } -func (vs *snapshotVisitor) OnRegistryStart() error { - return nil -} +func (vs *snapshotVisitor) OnRegistryStart() {} -func (vs *snapshotVisitor) OnRegistryFinished() error { +func (vs *snapshotVisitor) OnRegistryFinished() { if len(vs.level) > 0 { vs.dropName() } - return nil } -func (vs *snapshotVisitor) OnKey(name string) error { +func (vs *snapshotVisitor) OnKey(name string) { vs.level = append(vs.level, name) - return nil } -func (vs *snapshotVisitor) OnKeyNext() error { return nil } - func (vs *snapshotVisitor) getName() string { defer vs.dropName() if len(vs.level) == 1 { @@ -70,22 +64,18 @@ func (vs *snapshotVisitor) dropName() { vs.level = vs.level[:len(vs.level)-1] } -func (vs *snapshotVisitor) OnString(s string) error { +func (vs *snapshotVisitor) OnString(s string) { vs.snapshot.Strings[vs.getName()] = s - return nil } -func (vs *snapshotVisitor) OnBool(b bool) error { +func (vs *snapshotVisitor) OnBool(b bool) { vs.snapshot.Bools[vs.getName()] = b - return nil } -func (vs *snapshotVisitor) OnInt(i int64) error { +func (vs *snapshotVisitor) OnInt(i int64) { vs.snapshot.Ints[vs.getName()] = i - return nil } -func (vs *snapshotVisitor) OnFloat(f float64) error { +func (vs *snapshotVisitor) OnFloat(f float64) { vs.snapshot.Floats[vs.getName()] = f - return nil } diff --git a/libbeat/monitoring/visitor.go b/libbeat/monitoring/visitor.go index dffa1bfda78..7b1775dfb67 100644 --- a/libbeat/monitoring/visitor.go +++ b/libbeat/monitoring/visitor.go @@ -7,15 +7,34 @@ type Visitor interface { } type ValueVisitor interface { - OnString(s string) error - OnBool(b bool) error - OnInt(i int64) error - OnFloat(f float64) error + OnString(s string) + OnBool(b bool) + OnInt(i int64) + OnFloat(f float64) } type RegistryVisitor interface { - OnRegistryStart() error - OnRegistryFinished() error - OnKey(s string) error - OnKeyNext() error + OnRegistryStart() + OnRegistryFinished() + OnKey(s string) +} + +func ReportString(V Visitor, name string, value string) { + V.OnKey(name) + V.OnString(value) +} + +func ReportBool(V Visitor, name string, value bool) { + V.OnKey(name) + V.OnString(name) +} + +func ReportInt(V Visitor, name string, value int64) { + V.OnKey(name) + V.OnInt(value) +} + +func ReportFloat(V Visitor, name string, value float64) { + V.OnKey(name) + V.OnFloat(value) } diff --git a/libbeat/monitoring/visitor_expvar.go b/libbeat/monitoring/visitor_expvar.go index 51372c232ae..cdb67386650 100644 --- a/libbeat/monitoring/visitor_expvar.go +++ b/libbeat/monitoring/visitor_expvar.go @@ -16,25 +16,15 @@ func VisitExpvars(vs Visitor) { } func DoExpvars(f func(string, interface{})) { - VisitExpvars(NewKeyValueVisitor(func(name string, v interface{}) error { - f(name, v) - return nil - })) + VisitExpvars(NewKeyValueVisitor(f)) } func makeExparVisitor(level int, vs Visitor) func(expvar.KeyValue) { - first := true return func(kv expvar.KeyValue) { if ignoreExpvar(level, kv) { return } - if first { - first = false - } else { - vs.OnKeyNext() - } - name := kv.Key variable := kv.Value switch v := variable.(type) { diff --git a/libbeat/monitoring/visitor_kv.go b/libbeat/monitoring/visitor_kv.go index 13daf631579..32c6dbf3b41 100644 --- a/libbeat/monitoring/visitor_kv.go +++ b/libbeat/monitoring/visitor_kv.go @@ -3,32 +3,26 @@ package monitoring import "strings" type KeyValueVisitor struct { - cb func(key string, value interface{}) error + cb func(key string, value interface{}) level []string } -func NewKeyValueVisitor(cb func(string, interface{}) error) *KeyValueVisitor { +func NewKeyValueVisitor(cb func(string, interface{})) *KeyValueVisitor { return &KeyValueVisitor{cb: cb} } -func (vs *KeyValueVisitor) OnRegistryStart() error { - return nil -} +func (vs *KeyValueVisitor) OnRegistryStart() {} -func (vs *KeyValueVisitor) OnRegistryFinished() error { +func (vs *KeyValueVisitor) OnRegistryFinished() { if len(vs.level) > 0 { vs.dropName() } - return nil } -func (vs *KeyValueVisitor) OnKey(name string) error { +func (vs *KeyValueVisitor) OnKey(name string) { vs.level = append(vs.level, name) - return nil } -func (vs *KeyValueVisitor) OnKeyNext() error { return nil } - func (vs *KeyValueVisitor) getName() string { defer vs.dropName() if len(vs.level) == 1 { @@ -41,22 +35,8 @@ func (vs *KeyValueVisitor) dropName() { vs.level = vs.level[:len(vs.level)-1] } -func (vs *KeyValueVisitor) OnString(s string) error { - return vs.cb(vs.getName(), s) -} - -func (vs *KeyValueVisitor) OnBool(b bool) error { - return vs.cb(vs.getName(), b) -} - -func (vs *KeyValueVisitor) OnNil() error { - return vs.cb(vs.getName(), nil) -} - -func (vs *KeyValueVisitor) OnInt(i int64) error { - return vs.cb(vs.getName(), i) -} - -func (vs *KeyValueVisitor) OnFloat(f float64) error { - return vs.cb(vs.getName(), f) -} +func (vs *KeyValueVisitor) OnString(s string) { vs.cb(vs.getName(), s) } +func (vs *KeyValueVisitor) OnBool(b bool) { vs.cb(vs.getName(), b) } +func (vs *KeyValueVisitor) OnNil() { vs.cb(vs.getName(), nil) } +func (vs *KeyValueVisitor) OnInt(i int64) { vs.cb(vs.getName(), i) } +func (vs *KeyValueVisitor) OnFloat(f float64) { vs.cb(vs.getName(), f) } diff --git a/libbeat/service/service.go b/libbeat/service/service.go index 3abfaa3f7c9..760735bbdc1 100644 --- a/libbeat/service/service.go +++ b/libbeat/service/service.go @@ -94,7 +94,7 @@ func metricsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") first := true - report := func(key string, value interface{}) error { + report := func(key string, value interface{}) { if !first { fmt.Fprintf(w, ",\n") } @@ -104,7 +104,6 @@ func metricsHandler(w http.ResponseWriter, r *http.Request) { } else { fmt.Fprintf(w, "%q: %v", key, value) } - return nil } fmt.Fprintf(w, "{\n") From f0dc62d61b7210509929509b1d7f13739cc7e92b Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Thu, 2 Feb 2017 20:36:12 +0100 Subject: [PATCH 46/78] Add support for long and keyword in dict-type (#3515) As keyword is the default currently not mapping was added. For the human reader now a mapping is predefined. For long dict-types a dynamic mapping is added as is done for text. --- filebeat/filebeat.template-es2x.json | 3 +++ filebeat/filebeat.template.json | 3 +++ heartbeat/heartbeat.template-es2x.json | 3 +++ heartbeat/heartbeat.template.json | 3 +++ libbeat/scripts/generate_template.py | 23 +++++++++++++++++ metricbeat/metricbeat.template-es2x.json | 21 +++++++++++++++ metricbeat/metricbeat.template.json | 21 +++++++++++++++ packetbeat/packetbeat.template-es2x.json | 33 ++++++++++++++++++++++++ packetbeat/packetbeat.template.json | 33 ++++++++++++++++++++++++ winlogbeat/winlogbeat.template-es2x.json | 9 +++++++ winlogbeat/winlogbeat.template.json | 9 +++++++ 11 files changed, 161 insertions(+) diff --git a/filebeat/filebeat.template-es2x.json b/filebeat/filebeat.template-es2x.json index d2faed3a4d6..6c4fcd2ef2b 100644 --- a/filebeat/filebeat.template-es2x.json +++ b/filebeat/filebeat.template-es2x.json @@ -192,6 +192,9 @@ "index": "not_analyzed", "type": "string" }, + "fields": { + "properties": {} + }, "input_type": { "ignore_above": 1024, "index": "not_analyzed", diff --git a/filebeat/filebeat.template.json b/filebeat/filebeat.template.json index 5a9f8d9e3ea..a38e64921fa 100644 --- a/filebeat/filebeat.template.json +++ b/filebeat/filebeat.template.json @@ -161,6 +161,9 @@ "ignore_above": 1024, "type": "keyword" }, + "fields": { + "properties": {} + }, "input_type": { "ignore_above": 1024, "type": "keyword" diff --git a/heartbeat/heartbeat.template-es2x.json b/heartbeat/heartbeat.template-es2x.json index d3876d2dc47..e89bdf296df 100644 --- a/heartbeat/heartbeat.template-es2x.json +++ b/heartbeat/heartbeat.template-es2x.json @@ -67,6 +67,9 @@ } } }, + "fields": { + "properties": {} + }, "host": { "ignore_above": 1024, "index": "not_analyzed", diff --git a/heartbeat/heartbeat.template.json b/heartbeat/heartbeat.template.json index af7a39322ba..8cca9814722 100644 --- a/heartbeat/heartbeat.template.json +++ b/heartbeat/heartbeat.template.json @@ -54,6 +54,9 @@ } } }, + "fields": { + "properties": {} + }, "host": { "ignore_above": 1024, "type": "keyword" diff --git a/libbeat/scripts/generate_template.py b/libbeat/scripts/generate_template.py index 20bb99845e2..7756c080d97 100644 --- a/libbeat/scripts/generate_template.py +++ b/libbeat/scripts/generate_template.py @@ -279,6 +279,29 @@ def fill_field_properties(args, field, defaults, path): } }) + if field.get("dict-type") == "long": + if len(path) > 0: + name = path + "." + field["name"] + else: + name = field["name"] + + dynamic_templates.append({ + name: { + "mapping": { + "type": "long", + }, + "match_mapping_type": "long", + "path_match": name + ".*" + } + }) + + + properties[field["name"]] = { + "properties": {} + } + + + elif field.get("type") == "group": if len(path) > 0: path = path + "." + field["name"] diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 2eb265d1386..fa62799245d 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -19,6 +19,15 @@ }, "match_mapping_type": "string" } + }, + { + "system.process.cgroup.cpuacct.percpu": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "system.process.cgroup.cpuacct.percpu.*" + } } ], "properties": { @@ -918,6 +927,9 @@ } } }, + "fields": { + "properties": {} + }, "haproxy": { "properties": { "info": { @@ -1557,6 +1569,9 @@ "insync_replica": { "type": "boolean" }, + "isr": { + "properties": {} + }, "leader": { "type": "long" }, @@ -3523,6 +3538,9 @@ "index": "not_analyzed", "type": "string" }, + "percpu": { + "properties": {} + }, "stats": { "properties": { "system": { @@ -3816,6 +3834,9 @@ } } }, + "env": { + "properties": {} + }, "fd": { "properties": { "limit": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index f4da00f229a..beba10edf9b 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -13,6 +13,15 @@ }, "match_mapping_type": "string" } + }, + { + "system.process.cgroup.cpuacct.percpu": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "system.process.cgroup.cpuacct.percpu.*" + } } ], "properties": { @@ -917,6 +926,9 @@ } } }, + "fields": { + "properties": {} + }, "haproxy": { "properties": { "info": { @@ -1545,6 +1557,9 @@ "insync_replica": { "type": "boolean" }, + "isr": { + "properties": {} + }, "leader": { "type": "long" }, @@ -3491,6 +3506,9 @@ "ignore_above": 1024, "type": "keyword" }, + "percpu": { + "properties": {} + }, "stats": { "properties": { "system": { @@ -3780,6 +3798,9 @@ } } }, + "env": { + "properties": {} + }, "fd": { "properties": { "limit": { diff --git a/packetbeat/packetbeat.template-es2x.json b/packetbeat/packetbeat.template-es2x.json index aa02e310bbd..f44b4f49f88 100644 --- a/packetbeat/packetbeat.template-es2x.json +++ b/packetbeat/packetbeat.template-es2x.json @@ -32,6 +32,9 @@ "index": "not_analyzed", "type": "string" }, + "arguments": { + "properties": {} + }, "auto-delete": { "type": "boolean" }, @@ -90,6 +93,9 @@ "index": "not_analyzed", "type": "string" }, + "headers": { + "properties": {} + }, "if-empty": { "type": "boolean" }, @@ -567,6 +573,9 @@ } } }, + "supported": { + "properties": {} + }, "warnings": { "ignore_above": 1024, "index": "not_analyzed", @@ -854,6 +863,9 @@ "domloadtime": { "type": "long" }, + "fields": { + "properties": {} + }, "final": { "ignore_above": 1024, "index": "not_analyzed", @@ -875,6 +887,9 @@ }, "type": "string" }, + "headers": { + "properties": {} + }, "params": { "ignore_above": 1024, "index": "not_analyzed", @@ -894,6 +909,9 @@ "index": "not_analyzed", "type": "string" }, + "headers": { + "properties": {} + }, "phrase": { "ignore_above": 1024, "index": "not_analyzed", @@ -1001,6 +1019,9 @@ "initial": { "type": "long" }, + "keys": { + "properties": {} + }, "line": { "ignore_above": 1024, "index": "not_analyzed", @@ -1039,6 +1060,9 @@ "index": "not_analyzed", "type": "string" }, + "values": { + "properties": {} + }, "vbucket": { "type": "long" }, @@ -1071,6 +1095,9 @@ "flags": { "type": "long" }, + "keys": { + "properties": {} + }, "opaque": { "type": "long" }, @@ -1082,6 +1109,9 @@ "opcode_value": { "type": "long" }, + "stats": { + "properties": {} + }, "status": { "ignore_above": 1024, "index": "not_analyzed", @@ -1098,6 +1128,9 @@ "value": { "type": "long" }, + "values": { + "properties": {} + }, "version": { "ignore_above": 1024, "index": "not_analyzed", diff --git a/packetbeat/packetbeat.template.json b/packetbeat/packetbeat.template.json index 7db90b57cf0..5d9eb6e253d 100644 --- a/packetbeat/packetbeat.template.json +++ b/packetbeat/packetbeat.template.json @@ -25,6 +25,9 @@ "ignore_above": 1024, "type": "keyword" }, + "arguments": { + "properties": {} + }, "auto-delete": { "type": "boolean" }, @@ -75,6 +78,9 @@ "ignore_above": 1024, "type": "keyword" }, + "headers": { + "properties": {} + }, "if-empty": { "type": "boolean" }, @@ -491,6 +497,9 @@ } } }, + "supported": { + "properties": {} + }, "warnings": { "ignore_above": 1024, "type": "keyword" @@ -745,6 +754,9 @@ "domloadtime": { "type": "long" }, + "fields": { + "properties": {} + }, "final": { "ignore_above": 1024, "type": "keyword" @@ -761,6 +773,9 @@ "norms": false, "type": "text" }, + "headers": { + "properties": {} + }, "params": { "ignore_above": 1024, "type": "keyword" @@ -777,6 +792,9 @@ "ignore_above": 1024, "type": "keyword" }, + "headers": { + "properties": {} + }, "phrase": { "ignore_above": 1024, "type": "keyword" @@ -875,6 +893,9 @@ "initial": { "type": "long" }, + "keys": { + "properties": {} + }, "line": { "ignore_above": 1024, "type": "keyword" @@ -909,6 +930,9 @@ "ignore_above": 1024, "type": "keyword" }, + "values": { + "properties": {} + }, "vbucket": { "type": "long" }, @@ -939,6 +963,9 @@ "flags": { "type": "long" }, + "keys": { + "properties": {} + }, "opaque": { "type": "long" }, @@ -949,6 +976,9 @@ "opcode_value": { "type": "long" }, + "stats": { + "properties": {} + }, "status": { "ignore_above": 1024, "type": "keyword" @@ -963,6 +993,9 @@ "value": { "type": "long" }, + "values": { + "properties": {} + }, "version": { "ignore_above": 1024, "type": "keyword" diff --git a/winlogbeat/winlogbeat.template-es2x.json b/winlogbeat/winlogbeat.template-es2x.json index 4cff819e733..cb20ddb213d 100644 --- a/winlogbeat/winlogbeat.template-es2x.json +++ b/winlogbeat/winlogbeat.template-es2x.json @@ -54,9 +54,15 @@ "index": "not_analyzed", "type": "string" }, + "event_data": { + "properties": {} + }, "event_id": { "type": "long" }, + "fields": { + "properties": {} + }, "keywords": { "ignore_above": 1024, "index": "not_analyzed", @@ -192,6 +198,9 @@ } } }, + "user_data": { + "properties": {} + }, "version": { "type": "long" }, diff --git a/winlogbeat/winlogbeat.template.json b/winlogbeat/winlogbeat.template.json index 7772478ad1f..54653adaaf3 100644 --- a/winlogbeat/winlogbeat.template.json +++ b/winlogbeat/winlogbeat.template.json @@ -43,9 +43,15 @@ "ignore_above": 1024, "type": "keyword" }, + "event_data": { + "properties": {} + }, "event_id": { "type": "long" }, + "fields": { + "properties": {} + }, "keywords": { "ignore_above": 1024, "type": "keyword" @@ -156,6 +162,9 @@ } } }, + "user_data": { + "properties": {} + }, "version": { "type": "long" }, From ea12d92e01e42d1bc36ac87ec7ec1477e0e24da5 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Thu, 2 Feb 2017 21:34:06 +0100 Subject: [PATCH 47/78] CEPH module cleanup, phase 2 (#3509) * Split up health metricset into cluster and monitor metricset * Fix http json request header which I broke with my last PR * Add header support to http helper * Switch default host to local host * Add tests for the new metricsets * rename metricsets --- metricbeat/docs/fields.asciidoc | 43 +++--- metricbeat/docs/modules/ceph.asciidoc | 10 +- ...ealth.asciidoc => cluster_health.asciidoc} | 6 +- .../docs/modules/ceph/monitor_health.asciidoc | 19 +++ metricbeat/helper/http.go | 19 ++- metricbeat/include/list.go | 3 +- metricbeat/metricbeat.full.yml | 2 +- metricbeat/metricbeat.template-es2x.json | 142 +++++++++--------- metricbeat/metricbeat.template.json | 134 ++++++++--------- metricbeat/module/ceph/_meta/config.yml | 2 +- .../testdata/sample_response.json | 0 .../ceph/cluster_health/_meta/data.json | 26 ++++ .../ceph/cluster_health/_meta/docs.asciidoc | 3 + .../ceph/cluster_health/_meta/fields.yml | 21 +++ .../ceph/cluster_health/cluster_health.go | 54 +++++++ .../cluster_health_integration_test.go | 48 ++++++ .../cluster_health/cluster_health_test.go | 49 ++++++ metricbeat/module/ceph/cluster_health/data.go | 45 ++++++ metricbeat/module/ceph/health/_meta/data.json | 28 ---- .../module/ceph/health/_meta/docs.asciidoc | 3 - .../ceph/monitor_health/_meta/data.json | 46 ++++++ .../ceph/monitor_health/_meta/docs.asciidoc | 3 + .../_meta/fields.yml | 44 ++---- .../ceph/{health => monitor_health}/data.go | 66 +++----- .../monitor_health.go} | 17 ++- .../monitor_health_integration_test.go} | 9 +- .../monitor_health_test.go} | 25 +-- metricbeat/tests/system/test_ceph.py | 28 +++- 28 files changed, 587 insertions(+), 308 deletions(-) rename metricbeat/docs/modules/ceph/{health.asciidoc => cluster_health.asciidoc} (60%) create mode 100644 metricbeat/docs/modules/ceph/monitor_health.asciidoc rename metricbeat/module/ceph/{health => _meta}/testdata/sample_response.json (100%) create mode 100644 metricbeat/module/ceph/cluster_health/_meta/data.json create mode 100644 metricbeat/module/ceph/cluster_health/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/cluster_health/_meta/fields.yml create mode 100644 metricbeat/module/ceph/cluster_health/cluster_health.go create mode 100644 metricbeat/module/ceph/cluster_health/cluster_health_integration_test.go create mode 100644 metricbeat/module/ceph/cluster_health/cluster_health_test.go create mode 100644 metricbeat/module/ceph/cluster_health/data.go delete mode 100644 metricbeat/module/ceph/health/_meta/data.json delete mode 100644 metricbeat/module/ceph/health/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/monitor_health/_meta/data.json create mode 100644 metricbeat/module/ceph/monitor_health/_meta/docs.asciidoc rename metricbeat/module/ceph/{health => monitor_health}/_meta/fields.yml (52%) rename metricbeat/module/ceph/{health => monitor_health}/data.go (65%) rename metricbeat/module/ceph/{health/health.go => monitor_health/monitor_health.go} (68%) rename metricbeat/module/ceph/{health/health_integration_test.go => monitor_health/monitor_health_integration_test.go} (88%) rename metricbeat/module/ceph/{health/health_test.go => monitor_health/monitor_health_test.go} (74%) diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 6041737cebc..d1121ca37c9 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -428,14 +428,14 @@ experimental[] ceph Module [float] -== health Fields +== cluster_health Fields -health +cluster_health [float] -=== ceph.health.cluster.overall_status +=== ceph.cluster_health.overall_status type: keyword @@ -443,7 +443,7 @@ Overall status of the cluster [float] -=== ceph.health.cluster.timechecks.epoch +=== ceph.cluster_health.timechecks.epoch type: long @@ -451,7 +451,7 @@ Map version [float] -=== ceph.health.cluster.timechecks.round.value +=== ceph.cluster_health.timechecks.round.value type: long @@ -459,7 +459,7 @@ timecheck round [float] -=== ceph.health.cluster.timechecks.round.status +=== ceph.cluster_health.timechecks.round.status type: keyword @@ -467,7 +467,14 @@ Status of the round [float] -=== ceph.health.mon.available.pct +== monitor_health Fields + +monitor_health stats data + + + +[float] +=== ceph.monitor_health.available.pct type: long @@ -475,7 +482,7 @@ Available percent of the MON [float] -=== ceph.health.mon.health +=== ceph.monitor_health.health type: keyword @@ -483,7 +490,7 @@ Health of the MON [float] -=== ceph.health.mon.available.kb +=== ceph.monitor_health.available.kb type: long @@ -491,7 +498,7 @@ Available KB of the MON [float] -=== ceph.health.mon.total.kb +=== ceph.monitor_health.total.kb type: long @@ -499,7 +506,7 @@ Total KB of the MON [float] -=== ceph.health.mon.used.kb +=== ceph.monitor_health.used.kb type: long @@ -507,7 +514,7 @@ Used KB of the MON [float] -=== ceph.health.mon.last_updated +=== ceph.monitor_health.last_updated type: date @@ -515,7 +522,7 @@ Time when was updated [float] -=== ceph.health.mon.name +=== ceph.monitor_health.name type: keyword @@ -523,7 +530,7 @@ Name of the MON [float] -=== ceph.health.mon.store_stats.log.bytes +=== ceph.monitor_health.store_stats.log.bytes type: long @@ -533,7 +540,7 @@ Log bytes of MON [float] -=== ceph.health.mon.store_stats.misc.bytes +=== ceph.monitor_health.store_stats.misc.bytes type: long @@ -543,7 +550,7 @@ Misc bytes of MON [float] -=== ceph.health.mon.store_stats.sst.bytes +=== ceph.monitor_health.store_stats.sst.bytes type: long @@ -553,7 +560,7 @@ SST bytes of MON [float] -=== ceph.health.mon.store_stats.total.bytes +=== ceph.monitor_health.store_stats.total.bytes type: long @@ -563,7 +570,7 @@ Total bytes of MON [float] -=== ceph.health.mon.store_stats.last_updated +=== ceph.monitor_health.store_stats.last_updated type: long diff --git a/metricbeat/docs/modules/ceph.asciidoc b/metricbeat/docs/modules/ceph.asciidoc index 97265d9143d..17377a79684 100644 --- a/metricbeat/docs/modules/ceph.asciidoc +++ b/metricbeat/docs/modules/ceph.asciidoc @@ -19,7 +19,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: #- module: ceph -# metricsets: ["health"] +# metricsets: ["cluster_health", "monitor_health"] # enabled: true # period: 10s # hosts: ["localhost:5000"] @@ -30,7 +30,11 @@ metricbeat.modules: The following metricsets are available: -* <> +* <> -include::ceph/health.asciidoc[] +* <> + +include::ceph/cluster_health.asciidoc[] + +include::ceph/monitor_health.asciidoc[] diff --git a/metricbeat/docs/modules/ceph/health.asciidoc b/metricbeat/docs/modules/ceph/cluster_health.asciidoc similarity index 60% rename from metricbeat/docs/modules/ceph/health.asciidoc rename to metricbeat/docs/modules/ceph/cluster_health.asciidoc index 0c0765d4e4f..e8f087d772f 100644 --- a/metricbeat/docs/modules/ceph/health.asciidoc +++ b/metricbeat/docs/modules/ceph/cluster_health.asciidoc @@ -2,8 +2,8 @@ This file is generated! See scripts/docs_collector.py //// -[[metricbeat-metricset-ceph-health]] -include::../../../module/ceph/health/_meta/docs.asciidoc[] +[[metricbeat-metricset-ceph-cluster_health]] +include::../../../module/ceph/cluster_health/_meta/docs.asciidoc[] ==== Fields @@ -15,5 +15,5 @@ Here is an example document generated by this metricset: [source,json] ---- -include::../../../module/ceph/health/_meta/data.json[] +include::../../../module/ceph/cluster_health/_meta/data.json[] ---- diff --git a/metricbeat/docs/modules/ceph/monitor_health.asciidoc b/metricbeat/docs/modules/ceph/monitor_health.asciidoc new file mode 100644 index 00000000000..e03b1af0edf --- /dev/null +++ b/metricbeat/docs/modules/ceph/monitor_health.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-ceph-monitor_health]] +include::../../../module/ceph/monitor_health/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/ceph/monitor_health/_meta/data.json[] +---- diff --git a/metricbeat/helper/http.go b/metricbeat/helper/http.go index eb01376d2c1..f3eb49d5d37 100644 --- a/metricbeat/helper/http.go +++ b/metricbeat/helper/http.go @@ -12,15 +12,17 @@ import ( ) type HTTP struct { - base mb.BaseMetricSet - client *http.Client // HTTP client that is reused across requests. + base mb.BaseMetricSet + client *http.Client // HTTP client that is reused across requests. + headers map[string]string } // NewHTTP creates new http helper func NewHTTP(base mb.BaseMetricSet) *HTTP { return &HTTP{ - base: base, - client: &http.Client{Timeout: base.Module().Config().Timeout}, + base: base, + client: &http.Client{Timeout: base.Module().Config().Timeout}, + headers: map[string]string{}, } } @@ -32,6 +34,11 @@ func (h *HTTP) FetchResponse() (*http.Response, error) { if h.base.HostData().User != "" || h.base.HostData().Password != "" { req.SetBasicAuth(h.base.HostData().User, h.base.HostData().Password) } + + for k, v := range h.headers { + req.Header.Set(k, v) + } + resp, err := h.client.Do(req) if err != nil { return nil, fmt.Errorf("error making http request: %v", err) @@ -40,6 +47,10 @@ func (h *HTTP) FetchResponse() (*http.Response, error) { return resp, nil } +func (h *HTTP) SetHeader(key, value string) { + h.headers[key] = value +} + // FetchContent makes an HTTP request to the configured url and returns the body content. func (h *HTTP) FetchContent() ([]byte, error) { resp, err := h.FetchResponse() diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index c14b4487708..4e3b8d4a658 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -11,7 +11,8 @@ import ( _ "github.com/elastic/beats/metricbeat/module/apache" _ "github.com/elastic/beats/metricbeat/module/apache/status" _ "github.com/elastic/beats/metricbeat/module/ceph" - _ "github.com/elastic/beats/metricbeat/module/ceph/health" + _ "github.com/elastic/beats/metricbeat/module/ceph/cluster_health" + _ "github.com/elastic/beats/metricbeat/module/ceph/monitor_health" _ "github.com/elastic/beats/metricbeat/module/couchbase" _ "github.com/elastic/beats/metricbeat/module/couchbase/bucket" _ "github.com/elastic/beats/metricbeat/module/couchbase/cluster" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index d2e5899e121..38cc00c9cc1 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -96,7 +96,7 @@ metricbeat.modules: #-------------------------------- ceph Module -------------------------------- #- module: ceph -# metricsets: ["health"] +# metricsets: ["cluster_health", "monitor_health"] # enabled: true # period: 10s # hosts: ["localhost:5000"] diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index fa62799245d..83a3fa5c6cc 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -195,111 +195,107 @@ }, "ceph": { "properties": { - "health": { + "cluster_health": { "properties": { - "cluster": { + "overall_status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "timechecks": { "properties": { - "overall_status": { - "ignore_above": 1024, - "index": "not_analyzed", - "type": "string" + "epoch": { + "type": "long" }, - "timechecks": { + "round": { "properties": { - "epoch": { - "type": "long" + "status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" }, - "round": { - "properties": { - "status": { - "ignore_above": 1024, - "index": "not_analyzed", - "type": "string" - }, - "value": { - "type": "long" - } - } + "value": { + "type": "long" } } } } + } + } + }, + "monitor_health": { + "properties": { + "available": { + "properties": { + "kb": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + }, + "health": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "last_updated": { + "type": "date" }, - "mon": { + "name": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "store_stats": { "properties": { - "available": { + "last_updated": { + "type": "long" + }, + "log": { "properties": { - "kb": { - "type": "long" - }, - "pct": { + "bytes": { "type": "long" } } }, - "health": { - "ignore_above": 1024, - "index": "not_analyzed", - "type": "string" - }, - "last_updated": { - "type": "date" - }, - "name": { - "ignore_above": 1024, - "index": "not_analyzed", - "type": "string" - }, - "store_stats": { + "misc": { "properties": { - "last_updated": { + "bytes": { "type": "long" - }, - "log": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "misc": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "sst": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "total": { - "properties": { - "bytes": { - "type": "long" - } - } } } }, - "total": { + "sst": { "properties": { - "kb": { + "bytes": { "type": "long" } } }, - "used": { + "total": { "properties": { - "kb": { + "bytes": { "type": "long" } } } } + }, + "total": { + "properties": { + "kb": { + "type": "long" + } + } + }, + "used": { + "properties": { + "kb": { + "type": "long" + } + } } } } diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index beba10edf9b..f317f31acd3 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -196,107 +196,103 @@ }, "ceph": { "properties": { - "health": { + "cluster_health": { "properties": { - "cluster": { + "overall_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "timechecks": { "properties": { - "overall_status": { - "ignore_above": 1024, - "type": "keyword" + "epoch": { + "type": "long" }, - "timechecks": { + "round": { "properties": { - "epoch": { - "type": "long" + "status": { + "ignore_above": 1024, + "type": "keyword" }, - "round": { - "properties": { - "status": { - "ignore_above": 1024, - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "value": { + "type": "long" } } } } + } + } + }, + "monitor_health": { + "properties": { + "available": { + "properties": { + "kb": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + }, + "health": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" }, - "mon": { + "store_stats": { "properties": { - "available": { + "last_updated": { + "type": "long" + }, + "log": { "properties": { - "kb": { - "type": "long" - }, - "pct": { + "bytes": { "type": "long" } } }, - "health": { - "ignore_above": 1024, - "type": "keyword" - }, - "last_updated": { - "type": "date" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "store_stats": { + "misc": { "properties": { - "last_updated": { + "bytes": { "type": "long" - }, - "log": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "misc": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "sst": { - "properties": { - "bytes": { - "type": "long" - } - } - }, - "total": { - "properties": { - "bytes": { - "type": "long" - } - } } } }, - "total": { + "sst": { "properties": { - "kb": { + "bytes": { "type": "long" } } }, - "used": { + "total": { "properties": { - "kb": { + "bytes": { "type": "long" } } } } + }, + "total": { + "properties": { + "kb": { + "type": "long" + } + } + }, + "used": { + "properties": { + "kb": { + "type": "long" + } + } } } } diff --git a/metricbeat/module/ceph/_meta/config.yml b/metricbeat/module/ceph/_meta/config.yml index f783c6e88a5..f33044c8c0d 100644 --- a/metricbeat/module/ceph/_meta/config.yml +++ b/metricbeat/module/ceph/_meta/config.yml @@ -1,5 +1,5 @@ #- module: ceph -# metricsets: ["health"] +# metricsets: ["cluster_health", "monitor_health"] # enabled: true # period: 10s # hosts: ["localhost:5000"] diff --git a/metricbeat/module/ceph/health/testdata/sample_response.json b/metricbeat/module/ceph/_meta/testdata/sample_response.json similarity index 100% rename from metricbeat/module/ceph/health/testdata/sample_response.json rename to metricbeat/module/ceph/_meta/testdata/sample_response.json diff --git a/metricbeat/module/ceph/cluster_health/_meta/data.json b/metricbeat/module/ceph/cluster_health/_meta/data.json new file mode 100644 index 00000000000..15194a73132 --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/_meta/data.json @@ -0,0 +1,26 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "ceph": { + "cluster_health": { + "overall_status": "HEALTH_OK", + "timechecks": { + "epoch": 3, + "round": { + "status": "finished", + "value": 0 + } + } + } + }, + "metricset": { + "host": "127.0.0.1:5000", + "module": "ceph", + "name": "cluster_health", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/cluster_health/_meta/docs.asciidoc b/metricbeat/module/ceph/cluster_health/_meta/docs.asciidoc new file mode 100644 index 00000000000..8a878d05a14 --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== ceph cluster_health MetricSet + +This is the cluster_health metricset of the module ceph. diff --git a/metricbeat/module/ceph/cluster_health/_meta/fields.yml b/metricbeat/module/ceph/cluster_health/_meta/fields.yml new file mode 100644 index 00000000000..4a3729b84a3 --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/_meta/fields.yml @@ -0,0 +1,21 @@ +- name: cluster_health + type: group + description: > + cluster_health + fields: + - name: overall_status + type: keyword + description: > + Overall status of the cluster + - name: timechecks.epoch + type: long + description: > + Map version + - name: timechecks.round.value + type: long + description: > + timecheck round + - name: timechecks.round.status + type: keyword + description: > + Status of the round diff --git a/metricbeat/module/ceph/cluster_health/cluster_health.go b/metricbeat/module/ceph/cluster_health/cluster_health.go new file mode 100644 index 00000000000..064e78bfdfc --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/cluster_health.go @@ -0,0 +1,54 @@ +package cluster_health + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/api/v0.1/health" +) + +var ( + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() +) + +func init() { + if err := mb.Registry.AddMetricSet("ceph", "cluster_health", New, hostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + *helper.HTTP +} + +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The ceph cluster_health metricset is experimental") + + http := helper.NewHTTP(base) + http.SetHeader("Accept", "application/json") + + return &MetricSet{ + base, + http, + }, nil +} + +func (m *MetricSet) Fetch() (common.MapStr, error) { + + content, err := m.HTTP.FetchContent() + if err != nil { + return nil, err + } + + return eventMapping(content), nil +} diff --git a/metricbeat/module/ceph/cluster_health/cluster_health_integration_test.go b/metricbeat/module/ceph/cluster_health/cluster_health_integration_test.go new file mode 100644 index 00000000000..ea60f917210 --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/cluster_health_integration_test.go @@ -0,0 +1,48 @@ +package cluster_health + +import ( + "fmt" + "os" + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventFetcher(t, getConfig()) + err := mbtest.WriteEvent(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"cluster_health"}, + "hosts": getTestCephHost(), + } +} + +const ( + cephDefaultHost = "127.0.0.1" + cephDefaultPort = "5000" +) + +func getTestCephHost() string { + return fmt.Sprintf("%v:%v", + getenv("CEPH_HOST", cephDefaultHost), + getenv("CEPH_PORT", cephDefaultPort), + ) +} + +func getenv(name, defaultValue string) string { + return strDefault(os.Getenv(name), defaultValue) +} + +func strDefault(a, defaults string) string { + if len(a) == 0 { + return defaults + } + return a +} diff --git a/metricbeat/module/ceph/cluster_health/cluster_health_test.go b/metricbeat/module/ceph/cluster_health/cluster_health_test.go new file mode 100644 index 00000000000..f1b0ff7e88b --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/cluster_health_test.go @@ -0,0 +1,49 @@ +package cluster_health + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/common" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestFetchEventContents(t *testing.T) { + absPath, err := filepath.Abs("../_meta/testdata/") + assert.NoError(t, err) + + response, err := ioutil.ReadFile(absPath + "/sample_response.json") + assert.NoError(t, err) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json;") + w.Write([]byte(response)) + })) + defer server.Close() + + config := map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"cluster_health"}, + "hosts": []string{server.URL}, + } + + f := mbtest.NewEventFetcher(t, config) + event, err := f.Fetch() + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + assert.EqualValues(t, "HEALTH_OK", event["overall_status"]) + + timechecks := event["timechecks"].(common.MapStr) + assert.EqualValues(t, 3, timechecks["epoch"]) + + round := timechecks["round"].(common.MapStr) + assert.EqualValues(t, 0, round["value"]) + assert.EqualValues(t, "finished", round["status"]) +} diff --git a/metricbeat/module/ceph/cluster_health/data.go b/metricbeat/module/ceph/cluster_health/data.go new file mode 100644 index 00000000000..b0cf0707c1b --- /dev/null +++ b/metricbeat/module/ceph/cluster_health/data.go @@ -0,0 +1,45 @@ +package cluster_health + +import ( + "encoding/json" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type Timecheck struct { + RoundStatus string `json:"round_status"` + Epoch int64 `json:"epoch"` + Round int64 `json:"round"` +} + +type Output struct { + OverallStatus string `json:"overall_status"` + Timechecks Timecheck `json:"timechecks"` +} + +type HealthRequest struct { + Status string `json:"status"` + Output Output `json:"output"` +} + +func eventMapping(content []byte) common.MapStr { + + var d HealthRequest + err := json.Unmarshal(content, &d) + if err != nil { + logp.Err("Error: ", err) + } + + return common.MapStr{ + "overall_status": d.Output.OverallStatus, + "timechecks": common.MapStr{ + "epoch": d.Output.Timechecks.Epoch, + "round": common.MapStr{ + "value": d.Output.Timechecks.Round, + "status": d.Output.Timechecks.RoundStatus, + }, + }, + } + +} diff --git a/metricbeat/module/ceph/health/_meta/data.json b/metricbeat/module/ceph/health/_meta/data.json deleted file mode 100644 index b251ac646b2..00000000000 --- a/metricbeat/module/ceph/health/_meta/data.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "@timestamp": "2016-05-23T08:05:34.853Z", - "beat": { - "hostname": "host.example.com", - "name": "host.example.com" - }, - "ceph": { - "health": { - "cluster": { - "overall_stats": "HEALTH_OK", - "timechecks": { - "epoch": 3, - "round": { - "status": "finished", - "value": 0 - } - } - } - } - }, - "metricset": { - "host": "172.17.0.1:5000", - "module": "ceph", - "name": "health", - "rtt": 115 - }, - "type": "metricsets" -} \ No newline at end of file diff --git a/metricbeat/module/ceph/health/_meta/docs.asciidoc b/metricbeat/module/ceph/health/_meta/docs.asciidoc deleted file mode 100644 index 739729d5f1f..00000000000 --- a/metricbeat/module/ceph/health/_meta/docs.asciidoc +++ /dev/null @@ -1,3 +0,0 @@ -=== ceph health MetricSet - -This is the health metricset of the module ceph. diff --git a/metricbeat/module/ceph/monitor_health/_meta/data.json b/metricbeat/module/ceph/monitor_health/_meta/data.json new file mode 100644 index 00000000000..5674eb4f98f --- /dev/null +++ b/metricbeat/module/ceph/monitor_health/_meta/data.json @@ -0,0 +1,46 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "ceph": { + "monitor_health": { + "available": { + "kb": 45658840, + "pct": 69 + }, + "health": "HEALTH_OK", + "last_updated": "2017-01-31T16:10:38.389302Z", + "name": "ceph", + "store_stats": { + "last_updated": "0.000000", + "log": { + "bytes": 3065797 + }, + "misc": { + "bytes": 832 + }, + "sst": { + "bytes": 0 + }, + "total": { + "bytes": 3066629 + } + }, + "total": { + "kb": 65792556 + }, + "used": { + "kb": 16761940 + } + } + }, + "metricset": { + "host": "127.0.0.1:5000", + "module": "ceph", + "name": "monitor_health", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/monitor_health/_meta/docs.asciidoc b/metricbeat/module/ceph/monitor_health/_meta/docs.asciidoc new file mode 100644 index 00000000000..f27bcd604a7 --- /dev/null +++ b/metricbeat/module/ceph/monitor_health/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== ceph monitor_health MetricSet + +This is the monitor_health metricset of the module ceph. diff --git a/metricbeat/module/ceph/health/_meta/fields.yml b/metricbeat/module/ceph/monitor_health/_meta/fields.yml similarity index 52% rename from metricbeat/module/ceph/health/_meta/fields.yml rename to metricbeat/module/ceph/monitor_health/_meta/fields.yml index db52e92790f..753862383ca 100644 --- a/metricbeat/module/ceph/health/_meta/fields.yml +++ b/metricbeat/module/ceph/monitor_health/_meta/fields.yml @@ -1,73 +1,57 @@ -- name: health +- name: monitor_health type: group description: > - health + monitor_health stats data fields: - - name: cluster.overall_status - type: keyword - description: > - Overall status of the cluster - - name: cluster.timechecks.epoch - type: long - description: > - Map version - - name: cluster.timechecks.round.value - type: long - description: > - timecheck round - - name: cluster.timechecks.round.status - type: keyword - description: > - Status of the round - - name: mon.available.pct + - name: available.pct type: long description: > Available percent of the MON - - name: mon.health + - name: health type: keyword description: > Health of the MON - - name: mon.available.kb + - name: available.kb type: long description: > Available KB of the MON - - name: mon.total.kb + - name: total.kb type: long description: > Total KB of the MON - - name: mon.used.kb + - name: used.kb type: long description: > Used KB of the MON - - name: mon.last_updated + - name: last_updated type: date description: > Time when was updated - - name: mon.name + - name: name type: keyword description: > Name of the MON - - name: mon.store_stats.log.bytes + - name: store_stats.log.bytes type: long description: > Log bytes of MON format: bytes - - name: mon.store_stats.misc.bytes + - name: store_stats.misc.bytes type: long description: > Misc bytes of MON format: bytes - - name: mon.store_stats.sst.bytes + - name: store_stats.sst.bytes type: long description: > SST bytes of MON format: bytes - - name: mon.store_stats.total.bytes + - name: store_stats.total.bytes type: long description: > Total bytes of MON format: bytes - - name: mon.store_stats.last_updated + - name: store_stats.last_updated type: long description: > Last updated diff --git a/metricbeat/module/ceph/health/data.go b/metricbeat/module/ceph/monitor_health/data.go similarity index 65% rename from metricbeat/module/ceph/health/data.go rename to metricbeat/module/ceph/monitor_health/data.go index 646ea1e1a67..05077d47e9d 100644 --- a/metricbeat/module/ceph/health/data.go +++ b/metricbeat/module/ceph/monitor_health/data.go @@ -1,4 +1,4 @@ -package health +package monitor_health import ( "encoding/json" @@ -78,59 +78,41 @@ func eventsMapping(content []byte) []common.MapStr { events := []common.MapStr{} - event := common.MapStr{ - "cluster": common.MapStr{ - "overall_status": d.Output.OverallStatus, - "timechecks": common.MapStr{ - "epoch": d.Output.Timechecks.Epoch, - "round": common.MapStr{ - "value": d.Output.Timechecks.Round, - "status": d.Output.Timechecks.RoundStatus, - }, - }, - }, - } - - events = append(events, event) - for _, HealthService := range d.Output.Health.HealthServices { for _, Mon := range HealthService.Mons { event := common.MapStr{ - "mon": common.MapStr{ - "last_updated": Mon.LastUpdated, - "name": Mon.Name, - "available": common.MapStr{ - "pct": Mon.AvailPercent, - "kb": Mon.KbAvail, + "last_updated": Mon.LastUpdated, + "name": Mon.Name, + "available": common.MapStr{ + "pct": Mon.AvailPercent, + "kb": Mon.KbAvail, + }, + "total": common.MapStr{ + "kb": Mon.KbTotal, + }, + "health": Mon.Health, + "used": common.MapStr{ + "kb": Mon.KbUsed, + }, + "store_stats": common.MapStr{ + "log": common.MapStr{ + "bytes": Mon.StoreStats.BytesLog, }, - "total": common.MapStr{ - "kb": Mon.KbTotal, + "misc": common.MapStr{ + "bytes": Mon.StoreStats.BytesMisc, }, - "health": Mon.Health, - "used": common.MapStr{ - "kb": Mon.KbUsed, + "sst": common.MapStr{ + "bytes": Mon.StoreStats.BytesSSt, }, - "store_stats": common.MapStr{ - "log": common.MapStr{ - "bytes": Mon.StoreStats.BytesLog, - }, - "misc": common.MapStr{ - "bytes": Mon.StoreStats.BytesMisc, - }, - "sst": common.MapStr{ - "bytes": Mon.StoreStats.BytesSSt, - }, - "total": common.MapStr{ - "bytes": Mon.StoreStats.BytesTotal, - }, - "last_updated": Mon.StoreStats.LastUpdated, + "total": common.MapStr{ + "bytes": Mon.StoreStats.BytesTotal, }, + "last_updated": Mon.StoreStats.LastUpdated, }, } events = append(events, event) } - } return events diff --git a/metricbeat/module/ceph/health/health.go b/metricbeat/module/ceph/monitor_health/monitor_health.go similarity index 68% rename from metricbeat/module/ceph/health/health.go rename to metricbeat/module/ceph/monitor_health/monitor_health.go index ed16564e71c..8bcb68e515a 100644 --- a/metricbeat/module/ceph/health/health.go +++ b/metricbeat/module/ceph/monitor_health/monitor_health.go @@ -1,6 +1,8 @@ -package health +package monitor_health import ( + "fmt" + "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/helper" @@ -21,7 +23,7 @@ var ( ) func init() { - if err := mb.Registry.AddMetricSet("ceph", "health", New, hostParser); err != nil { + if err := mb.Registry.AddMetricSet("ceph", "monitor_health", New, hostParser); err != nil { panic(err) } } @@ -32,11 +34,14 @@ type MetricSet struct { } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The ceph health metricset is experimental") + logp.Warn("EXPERIMENTAL: The ceph monitor_health metricset is experimental") + + http := helper.NewHTTP(base) + http.SetHeader("Accept", "application/json") return &MetricSet{ base, - helper.NewHTTP(base), + http, }, nil } @@ -47,5 +52,9 @@ func (m *MetricSet) Fetch() ([]common.MapStr, error) { return nil, err } + fmt.Printf("%+v", string(content)) + fmt.Printf("%+v", eventsMapping(content)) + return eventsMapping(content), nil + } diff --git a/metricbeat/module/ceph/health/health_integration_test.go b/metricbeat/module/ceph/monitor_health/monitor_health_integration_test.go similarity index 88% rename from metricbeat/module/ceph/health/health_integration_test.go rename to metricbeat/module/ceph/monitor_health/monitor_health_integration_test.go index 3380739960a..9760b882ce7 100644 --- a/metricbeat/module/ceph/health/health_integration_test.go +++ b/metricbeat/module/ceph/monitor_health/monitor_health_integration_test.go @@ -1,10 +1,11 @@ -package health +package monitor_health import ( "fmt" - mbtest "github.com/elastic/beats/metricbeat/mb/testing" "os" "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" ) func TestData(t *testing.T) { @@ -18,13 +19,13 @@ func TestData(t *testing.T) { func getConfig() map[string]interface{} { return map[string]interface{}{ "module": "ceph", - "metricsets": []string{"health"}, + "metricsets": []string{"monitor_health"}, "hosts": getTestCephHost(), } } const ( - cephDefaultHost = "172.17.0.1" + cephDefaultHost = "127.0.0.1" cephDefaultPort = "5000" ) diff --git a/metricbeat/module/ceph/health/health_test.go b/metricbeat/module/ceph/monitor_health/monitor_health_test.go similarity index 74% rename from metricbeat/module/ceph/health/health_test.go rename to metricbeat/module/ceph/monitor_health/monitor_health_test.go index c1058d62bf7..bf78b3a6052 100644 --- a/metricbeat/module/ceph/health/health_test.go +++ b/metricbeat/module/ceph/monitor_health/monitor_health_test.go @@ -1,4 +1,4 @@ -package health +package monitor_health import ( "io/ioutil" @@ -14,7 +14,7 @@ import ( ) func TestFetchEventContents(t *testing.T) { - absPath, err := filepath.Abs("./testdata/") + absPath, err := filepath.Abs("../_meta/testdata/") response, err := ioutil.ReadFile(absPath + "/sample_response.json") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -26,7 +26,7 @@ func TestFetchEventContents(t *testing.T) { config := map[string]interface{}{ "module": "ceph", - "metricsets": []string{"health"}, + "metricsets": []string{"monitor_health"}, "hosts": []string{server.URL}, } @@ -39,24 +39,7 @@ func TestFetchEventContents(t *testing.T) { t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) - cluster := event["cluster"].(common.MapStr) - assert.EqualValues(t, "HEALTH_OK", cluster["overall_status"]) - - timechecks := cluster["timechecks"].(common.MapStr) - assert.EqualValues(t, 3, timechecks["epoch"]) - - round := timechecks["round"].(common.MapStr) - assert.EqualValues(t, 0, round["value"]) - assert.EqualValues(t, "finished", round["status"]) - - event = events[1] - if !assert.NoError(t, err) { - t.FailNow() - } - - t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) - - mon := event["mon"].(common.MapStr) + mon := event assert.EqualValues(t, "HEALTH_OK", mon["health"]) assert.EqualValues(t, "ceph", mon["name"]) assert.EqualValues(t, "2017-01-19 11:34:50.700723 +0000 UTC", mon["last_updated"].(Tick).Time.String()) diff --git a/metricbeat/tests/system/test_ceph.py b/metricbeat/tests/system/test_ceph.py index 34a9229695d..b740f6f73f8 100644 --- a/metricbeat/tests/system/test_ceph.py +++ b/metricbeat/tests/system/test_ceph.py @@ -5,13 +5,35 @@ class Test(metricbeat.BaseTest): @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") - def test_health(self): + def test_cluster_health(self): """ - ceph health metricset test + ceph cluster_health metricset test """ self.render_config_template(modules=[{ "name": "ceph", - "metricsets": ["health"], + "metricsets": ["cluster_health"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_monitor_health(self): + """ + ceph monitor_health metricset test + """ + self.render_config_template(modules=[{ + "name": "ceph", + "metricsets": ["monitor_health"], "hosts": self.get_hosts(), "period": "1s" }]) From 81239104280d43559eb170b1b4cf585c44469a4a Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Thu, 2 Feb 2017 23:33:44 -0800 Subject: [PATCH 48/78] Add new config options to the decode_json_fields processor docs (#3512) --- libbeat/docs/processors-config.asciidoc | 9 +++++++++ libbeat/docs/processors.asciidoc | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libbeat/docs/processors-config.asciidoc b/libbeat/docs/processors-config.asciidoc index 9525d6310fe..b7a71bc17c9 100644 --- a/libbeat/docs/processors-config.asciidoc +++ b/libbeat/docs/processors-config.asciidoc @@ -353,6 +353,8 @@ processors: fields: ["field1", "field2", ...] process_array: false max_depth: 1 + target: + overwrite_keys: false ----------------------------------------------------- The `decode_json_fields` processor has the following configuration settings: @@ -361,6 +363,13 @@ The `decode_json_fields` processor has the following configuration settings: `process_array`:: (Optional) A boolean that specifies whether to process arrays. The default is false. `max_depth`:: (Optional) The maximum parsing depth. The default is 1. +`target`:: (Optional) The field under which the decoded JSON will be written. By +default the decoded JSON object replaces the string field from which it was +read. To merge the decoded JSON fields into the root of the event, specify +`target` with an empty value (`target:`). +`overwrite_keys`:: (Optional) A boolean that specifies whether keys that already +exist in the event are overwritten by keys from the decoded JSON object. The +default value is false. [[drop-event]] === drop_event diff --git a/libbeat/docs/processors.asciidoc b/libbeat/docs/processors.asciidoc index 923c1e5b1b9..c393f16f9e8 100644 --- a/libbeat/docs/processors.asciidoc +++ b/libbeat/docs/processors.asciidoc @@ -6,7 +6,7 @@ //// Use the appropriate variables defined in the index.asciidoc file to //// resolve Beat names: beatname_uc and beatname_lc. //// Use the following include to pull this content into a doc file: -//// include::../../libbeat/docs/filtering.asciidoc[] +//// include::../../libbeat/docs/processors.asciidoc[] ////////////////////////////////////////////////////////////////////////// You can define processors in your configuration to process events before they From 0d647373ce431e77e4a96cca86754840c430ef00 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Fri, 3 Feb 2017 09:38:13 +0100 Subject: [PATCH 49/78] Make -setup load the Beat dashboards (#3506) With this change, `filebeat -setup` loads the sample Kibana dashboards, being the rough equivalent of running: `scripts/import_dashboards && filebeat`. This works for all Beats, not only Filebeat. The `import_dashboards` program is kept for now, to avoid braking compatibility. Code wise, the two share most of the code, which was separated in a sub-package. All flags available for the `import_dashboard` are available as go-ucfg options. For example, to load the dashboards from a directory you can do: filebeat -e -setup -E "dashboards.dir=_meta/kibana" Or do load the snapshot version of the dashboards: filebeat -e -setup -E "dashboards.snapshot=true" This also changes the pipeline loading to happen automatically regardless of whether `-setup` was used or not. Part of #3159. Contains updates for the docs and changelog. --- CHANGELOG.asciidoc | 1 + filebeat/beater/filebeat.go | 21 +- .../docs/reference/configuration.asciidoc | 1 + .../configuration/filebeat-options.asciidoc | 3 + filebeat/filebeat.full.yml | 39 ++ filebeat/fileset/modules.go | 12 +- filebeat/fileset/modules_integration_test.go | 2 +- filebeat/tests/system/test_modules.py | 2 +- .../docs/reference/configuration.asciidoc | 1 + .../configuration/heartbeat-options.asciidoc | 3 + heartbeat/heartbeat.full.yml | 39 ++ libbeat/_meta/config.full.yml | 39 ++ libbeat/beat/beat.go | 42 ++ libbeat/dashboards/dashboards/config.go | 23 + libbeat/dashboards/dashboards/dashboards.go | 42 ++ libbeat/dashboards/dashboards/importer.go | 535 +++++++++++++++++ .../dashboards/importer_integration_test.go | 55 ++ .../testdata/testbeat-dashboards.zip | Bin 0 -> 3005 bytes libbeat/dashboards/import_dashboards.go | 549 +----------------- libbeat/docs/dashboardsconfig.asciidoc | 87 +++ libbeat/docs/shared-command-line.asciidoc | 4 + .../docs/reference/configuration.asciidoc | 1 + .../configuration/metricbeat-options.asciidoc | 3 + metricbeat/metricbeat.full.yml | 39 ++ .../docs/reference/configuration.asciidoc | 1 + .../configuration/packetbeat-options.asciidoc | 3 + packetbeat/packetbeat.full.yml | 39 ++ .../docs/reference/configuration.asciidoc | 1 + .../configuration/winlogbeat-options.asciidoc | 3 + winlogbeat/winlogbeat.full.yml | 39 ++ 30 files changed, 1095 insertions(+), 534 deletions(-) create mode 100644 libbeat/dashboards/dashboards/config.go create mode 100644 libbeat/dashboards/dashboards/dashboards.go create mode 100644 libbeat/dashboards/dashboards/importer.go create mode 100644 libbeat/dashboards/dashboards/importer_integration_test.go create mode 100644 libbeat/dashboards/dashboards/testdata/testbeat-dashboards.zip create mode 100644 libbeat/docs/dashboardsconfig.asciidoc diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index f54b760616f..e8edbd73d80 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -66,6 +66,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - RPM/deb packages will now install the config file with 0600 permissions. {pull}3382[3382] - Add the option to pass custom HTTP headers to the Elasticsearch output. {pull}3400[3400] - Unify `regexp` and `contains` conditionals, for both to support array of strings and convert numbers to strings if required. {pull}3469[3469] +- Add the option to load the sample dashboards during the Beat startup phase. {pull}3506[3506] *Metricbeat* diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index 1249fb712f9..8c2c6a5d03d 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -20,8 +20,7 @@ import ( ) var ( - once = flag.Bool("once", false, "Run filebeat only once until all harvesters reach EOF") - setup = flag.Bool("setup", false, "Run the setup phase for the modules") + once = flag.Bool("once", false, "Run filebeat only once until all harvesters reach EOF") ) // Filebeat is a beater object. Contains all objects needed to run the beat @@ -71,11 +70,12 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) { return fb, nil } -// Setup is called on user request (the -setup flag) to do the initial Beat setup. -func (fb *Filebeat) Setup(b *beat.Beat) error { +// modulesSetup is called when modules are configured to do the initial +// setup. +func (fb *Filebeat) modulesSetup(b *beat.Beat) error { esConfig := b.Config.Output["elasticsearch"] if esConfig == nil || !esConfig.Enabled() { - return fmt.Errorf("Setup requested but the Elasticsearch output is not configured/enabled") + return fmt.Errorf("Filebeat modules configured but the Elasticsearch output is not configured/enabled") } esClient, err := elasticsearch.NewConnectedClient(esConfig) if err != nil { @@ -83,7 +83,12 @@ func (fb *Filebeat) Setup(b *beat.Beat) error { } defer esClient.Close() - return fb.moduleRegistry.Setup(esClient) + err = fb.moduleRegistry.LoadPipelines(esClient) + if err != nil { + return err + } + + return nil } // Run allows the beater to be run as a beat. @@ -91,8 +96,8 @@ func (fb *Filebeat) Run(b *beat.Beat) error { var err error config := fb.config - if *setup { - err = fb.Setup(b) + if !fb.moduleRegistry.Empty() { + err = fb.modulesSetup(b) if err != nil { return err } diff --git a/filebeat/docs/reference/configuration.asciidoc b/filebeat/docs/reference/configuration.asciidoc index 637b01c2587..46c883a7da9 100644 --- a/filebeat/docs/reference/configuration.asciidoc +++ b/filebeat/docs/reference/configuration.asciidoc @@ -21,6 +21,7 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> * <> +* <> * <> * <> diff --git a/filebeat/docs/reference/configuration/filebeat-options.asciidoc b/filebeat/docs/reference/configuration/filebeat-options.asciidoc index 4edcd59468a..a048747a01a 100644 --- a/filebeat/docs/reference/configuration/filebeat-options.asciidoc +++ b/filebeat/docs/reference/configuration/filebeat-options.asciidoc @@ -588,6 +588,9 @@ include::../../../../libbeat/docs/outputconfig.asciidoc[] pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] +pass::[] +include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] + pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] diff --git a/filebeat/filebeat.full.yml b/filebeat/filebeat.full.yml index 8b35c37ebab..f61a8f24be9 100644 --- a/filebeat/filebeat.full.yml +++ b/filebeat/filebeat.full.yml @@ -897,6 +897,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: filebeat + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, diff --git a/filebeat/fileset/modules.go b/filebeat/fileset/modules.go index 4dba8132b80..2bdc3631e31 100644 --- a/filebeat/fileset/modules.go +++ b/filebeat/fileset/modules.go @@ -247,8 +247,8 @@ type PipelineLoader interface { LoadJSON(path string, json map[string]interface{}) error } -// Setup is called on -setup and loads the pipelines for each configured fileset. -func (reg *ModuleRegistry) Setup(esClient PipelineLoader) error { +// LoadPipelines loads the pipelines for each configured fileset. +func (reg *ModuleRegistry) LoadPipelines(esClient PipelineLoader) error { for module, filesets := range reg.registry { for name, fileset := range filesets { pipelineID, content, err := fileset.GetPipeline() @@ -273,3 +273,11 @@ func loadPipeline(esClient PipelineLoader, pipelineID string, content map[string logp.Info("Elasticsearch pipeline with ID '%s' loaded", pipelineID) return nil } + +func (reg *ModuleRegistry) Empty() bool { + count := 0 + for _, filesets := range reg.registry { + count += len(filesets) + } + return count == 0 +} diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index 129ef897666..acfc98d7b90 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -48,7 +48,7 @@ func TestSetupNginx(t *testing.T) { reg, err := newModuleRegistry(modulesPath, configs, nil) assert.NoError(t, err) - err = reg.Setup(client) + err = reg.LoadPipelines(client) assert.NoError(t, err) status, _, _ := client.Request("GET", "/_ingest/pipeline/nginx-access-with_plugins", "", nil, nil) diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 304742edf45..4dc7240b9a5 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -71,7 +71,7 @@ def run_on_file(self, module, fileset, test_file, cfgfile): cmd = [ self.filebeat, "-systemTest", - "-e", "-d", "*", "-once", "-setup", + "-e", "-d", "*", "-once", "-c", cfgfile, "-modules={}".format(module), "-M", "{module}.{fileset}.var.paths=[{test_file}]".format( diff --git a/heartbeat/docs/reference/configuration.asciidoc b/heartbeat/docs/reference/configuration.asciidoc index 840e59fc58a..1726e1fc77b 100644 --- a/heartbeat/docs/reference/configuration.asciidoc +++ b/heartbeat/docs/reference/configuration.asciidoc @@ -19,6 +19,7 @@ configuration settings, you need to restart Heartbeat to pick up the changes. * <> * <> * <> +* <> * <> * <> diff --git a/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc b/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc index 8a10cc061f1..ac845e29bd6 100644 --- a/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc +++ b/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc @@ -455,6 +455,9 @@ include::../../../../libbeat/docs/outputconfig.asciidoc[] pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] +pass::[] +include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] + pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] diff --git a/heartbeat/heartbeat.full.yml b/heartbeat/heartbeat.full.yml index 563b02ecdef..3155f7aa015 100644 --- a/heartbeat/heartbeat.full.yml +++ b/heartbeat/heartbeat.full.yml @@ -745,6 +745,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: heartbeat + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, diff --git a/libbeat/_meta/config.full.yml b/libbeat/_meta/config.full.yml index 05087a79d4c..1a89c33cc87 100644 --- a/libbeat/_meta/config.full.yml +++ b/libbeat/_meta/config.full.yml @@ -547,6 +547,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: beatname + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, diff --git a/libbeat/beat/beat.go b/libbeat/beat/beat.go index 13b0f2b7b36..2d9d2a35a4a 100644 --- a/libbeat/beat/beat.go +++ b/libbeat/beat/beat.go @@ -44,7 +44,9 @@ import ( "github.com/elastic/beats/libbeat/cfgfile" "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/dashboards/dashboards" "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/outputs/elasticsearch" "github.com/elastic/beats/libbeat/paths" "github.com/elastic/beats/libbeat/plugin" "github.com/elastic/beats/libbeat/processors" @@ -100,10 +102,12 @@ type BeatConfig struct { Logging logp.Logging `config:"logging"` Processors processors.PluginConfig `config:"processors"` Path paths.Path `config:"path"` + Dashboards *common.Config `config:"dashboards"` } var ( printVersion = flag.Bool("version", false, "Print the version and exit") + setup = flag.Bool("setup", false, "Load the sample Kibana dashboards") ) var debugf = logp.MakeDebug("beat") @@ -209,6 +213,11 @@ func (b *Beat) launch(bt Creator) error { svc.HandleSignals(beater.Stop) + err = b.loadDashboards() + if err != nil { + return err + } + logp.Info("%s start running.", b.Name) defer logp.Info("%s stopped.", b.Name) defer logp.LogTotalExpvars(&b.Config.Logging) @@ -285,6 +294,39 @@ func (b *Beat) configure() error { return nil } +func (b *Beat) loadDashboards() error { + if *setup { + // -setup implies dashboards.enabled=true + if b.Config.Dashboards == nil { + b.Config.Dashboards = common.NewConfig() + } + err := b.Config.Dashboards.SetBool("enabled", -1, true) + if err != nil { + return fmt.Errorf("Error setting dashboard.enabled=true: %v", err) + } + } + + if b.Config.Dashboards != nil && b.Config.Dashboards.Enabled() { + esConfig := b.Config.Output["elasticsearch"] + if esConfig == nil || !esConfig.Enabled() { + return fmt.Errorf("Dashboard loading requested but the Elasticsearch output is not configured/enabled") + } + esClient, err := elasticsearch.NewConnectedClient(esConfig) + if err != nil { + return fmt.Errorf("Error creating ES client: %v", err) + } + defer esClient.Close() + + err = dashboards.ImportDashboards(b.Name, b.Version, esClient, b.Config.Dashboards) + if err != nil { + return fmt.Errorf("Error importing Kibana dashboards: %v", err) + } + logp.Info("Kibana dashboards successfully loaded.") + } + + return nil +} + // handleError handles the given error by logging it and then returning the // error. If the err is nil or is a GracefulExit error then the method will // return nil without logging anything. diff --git a/libbeat/dashboards/dashboards/config.go b/libbeat/dashboards/dashboards/config.go new file mode 100644 index 00000000000..d3abebf37f3 --- /dev/null +++ b/libbeat/dashboards/dashboards/config.go @@ -0,0 +1,23 @@ +package dashboards + +type DashboardsConfig struct { + Enabled bool `config:"enabled"` + KibanaIndex string `config:"kibana_index"` + Index string `config:"index"` + Dir string `config:"directory"` + File string `config:"file"` + Beat string `config:"beat"` + URL string `config:"url"` + OnlyDashboards bool `config:"only_dashboards"` + OnlyIndex bool `config:"only_index"` + Snapshot bool `config:"snapshot"` + SnapshotURL string `config:"snapshot_url"` +} + +var defaultDashboardsConfig = DashboardsConfig{ + KibanaIndex: ".kibana", +} +var ( + defaultURLPattern = "https://artifacts.elastic.co/downloads/beats/beats-dashboards/beats-dashboards-%s.zip" + snapshotURLPattern = "https://beats-nightlies.s3.amazonaws.com/dashboards/beats-dashboards-%s-SNAPSHOT.zip" +) diff --git a/libbeat/dashboards/dashboards/dashboards.go b/libbeat/dashboards/dashboards/dashboards.go new file mode 100644 index 00000000000..7a99696253f --- /dev/null +++ b/libbeat/dashboards/dashboards/dashboards.go @@ -0,0 +1,42 @@ +package dashboards + +import ( + "fmt" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/outputs/elasticsearch" +) + +// DashboardLoader is a subset of the Elasticsearch client API capable of +// loading the dashboards. +type DashboardLoader interface { + LoadJSON(path string, json map[string]interface{}) error + CreateIndex(index string, body interface{}) (int, *elasticsearch.QueryResult, error) +} + +func ImportDashboards(beatName, beatVersion string, esClient DashboardLoader, cfg *common.Config) error { + if cfg == nil || !cfg.Enabled() { + return nil + } + + dashConfig := defaultDashboardsConfig + dashConfig.Beat = beatName + dashConfig.URL = fmt.Sprintf(defaultURLPattern, beatVersion) + dashConfig.SnapshotURL = fmt.Sprintf(snapshotURLPattern, beatVersion) + + err := cfg.Unpack(&dashConfig) + if err != nil { + return err + } + + importer, err := NewImporter(&dashConfig, esClient, nil) + if err != nil { + return nil + } + + if err := importer.Import(); err != nil { + return err + } + + return nil +} diff --git a/libbeat/dashboards/dashboards/importer.go b/libbeat/dashboards/dashboards/importer.go new file mode 100644 index 00000000000..ad3150fa8ba --- /dev/null +++ b/libbeat/dashboards/dashboards/importer.go @@ -0,0 +1,535 @@ +package dashboards + +import ( + "archive/zip" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +// MessageOutputter is a function type for injecting status logging +// into this module. +type MessageOutputter func(msg string, a ...interface{}) + +type Importer struct { + cfg *DashboardsConfig + client DashboardLoader + msgOutputter *MessageOutputter +} + +func NewImporter(cfg *DashboardsConfig, client DashboardLoader, msgOutputter *MessageOutputter) (*Importer, error) { + return &Importer{ + cfg: cfg, + client: client, + msgOutputter: msgOutputter, + }, nil +} + +func (imp Importer) statusMsg(msg string, a ...interface{}) { + if imp.msgOutputter != nil { + (*imp.msgOutputter)(msg, a...) + } else { + logp.Debug("dashboards", msg, a...) + } +} + +// Import imports the Kibana dashboards according to the configuration options. +func (imp Importer) Import() error { + + err := imp.CreateKibanaIndex() + if err != nil { + return fmt.Errorf("Error creating Kibana index: %v", err) + } + + if imp.cfg.Dir != "" { + err = imp.ImportKibana(imp.cfg.Dir) + if err != nil { + return fmt.Errorf("Error importing directory %s: %v", imp.cfg.Dir, err) + } + } else { + if imp.cfg.URL != "" || imp.cfg.Snapshot || imp.cfg.File != "" { + err = imp.ImportArchive() + if err != nil { + return fmt.Errorf("Error importing URL/file: %v", err) + } + } else { + return fmt.Errorf("No URL and no file specify. Nothing to import") + } + } + return nil +} + +// CreateKibanaIndex creates the kibana index if it doesn't exists and sets +// some index properties which are needed as a workaround for: +// https://github.com/elastic/beats-dashboards/issues/94 +func (imp Importer) CreateKibanaIndex() error { + imp.client.CreateIndex(imp.cfg.KibanaIndex, nil) + _, _, err := imp.client.CreateIndex(imp.cfg.KibanaIndex+"/_mapping/search", + common.MapStr{ + "search": common.MapStr{ + "properties": common.MapStr{ + "hits": common.MapStr{ + "type": "integer", + }, + "version": common.MapStr{ + "type": "integer", + }, + }, + }, + }) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("Failed to set the mapping - %s", err)) + } + return nil +} + +func (imp Importer) ImportJSONFile(fileType string, file string) error { + + path := "/" + imp.cfg.KibanaIndex + "/" + fileType + + reader, err := ioutil.ReadFile(file) + if err != nil { + return fmt.Errorf("Failed to read %s. Error: %s", file, err) + } + var jsonContent map[string]interface{} + json.Unmarshal(reader, &jsonContent) + fileBase := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) + + err = imp.client.LoadJSON(path+"/"+fileBase, jsonContent) + if err != nil { + return fmt.Errorf("Failed to load %s under %s/%s: %s", file, path, fileBase, err) + } + + return nil +} + +func (imp Importer) ImportDashboard(file string) error { + + imp.statusMsg("Import dashboard %s", file) + + /* load dashboard */ + err := imp.ImportJSONFile("dashboard", file) + if err != nil { + return err + } + + /* load the visualizations and searches that depend on the dashboard */ + err = imp.importPanelsFromDashboard(file) + if err != nil { + return err + } + + return nil +} + +func (imp Importer) importPanelsFromDashboard(file string) (err error) { + + // directory with the dashboards + dir := filepath.Dir(file) + + // main directory with dashboard, search, visualizations directories + mainDir := filepath.Dir(dir) + + reader, err := ioutil.ReadFile(file) + if err != nil { + return + } + type record struct { + Title string `json:"title"` + PanelsJSON string `json:"panelsJSON"` + } + type panel struct { + ID string `json:"id"` + Type string `json:"type"` + } + + var jsonContent record + json.Unmarshal(reader, &jsonContent) + + var widgets []panel + json.Unmarshal([]byte(jsonContent.PanelsJSON), &widgets) + + for _, widget := range widgets { + + if widget.Type == "visualization" { + err = imp.ImportVisualization(path.Join(mainDir, "visualization", widget.ID+".json")) + if err != nil { + return err + } + } else if widget.Type == "search" { + err = imp.ImportSearch(path.Join(mainDir, "search", widget.ID+".json")) + if err != nil { + return err + } + } else { + imp.statusMsg("Widgets: %v", widgets) + return fmt.Errorf("Unknown panel type %s in %s", widget.Type, file) + } + } + return +} + +func (imp Importer) importSearchFromVisualization(file string) error { + type record struct { + Title string `json:"title"` + SavedSearchID string `json:"savedSearchId"` + } + + reader, err := ioutil.ReadFile(file) + if err != nil { + return nil + } + + var jsonContent record + json.Unmarshal(reader, &jsonContent) + id := jsonContent.SavedSearchID + if len(id) == 0 { + // no search used + return nil + } + + // directory with the visualizations + dir := filepath.Dir(file) + + // main directory + mainDir := filepath.Dir(dir) + + searchFile := path.Join(mainDir, "search", id+".json") + + if searchFile != "" { + // visualization depends on search + if err := imp.ImportSearch(searchFile); err != nil { + return err + } + } + return nil +} + +func (imp Importer) ImportVisualization(file string) error { + + imp.statusMsg("Import visualization %s", file) + if err := imp.ImportJSONFile("visualization", file); err != nil { + return err + } + + err := imp.importSearchFromVisualization(file) + if err != nil { + return err + } + return nil +} + +func (imp Importer) ImportSearch(file string) error { + + reader, err := ioutil.ReadFile(file) + if err != nil { + return err + } + searchName := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) + + var searchContent common.MapStr + err = json.Unmarshal(reader, &searchContent) + if err != nil { + return fmt.Errorf("Failed to unmarshal search content %s: %v", searchName, err) + } + + if imp.cfg.Index != "" { + + // change index pattern name + if savedObject, ok := searchContent["kibanaSavedObjectMeta"].(map[string]interface{}); ok { + if source, ok := savedObject["searchSourceJSON"].(string); ok { + var record common.MapStr + err = json.Unmarshal([]byte(source), &record) + if err != nil { + return fmt.Errorf("Failed to unmarshal searchSourceJSON from search %s: %v", searchName, err) + } + + if _, ok := record["index"]; ok { + record["index"] = imp.cfg.Index + } + searchSourceJSON, err := json.Marshal(record) + if err != nil { + return fmt.Errorf("Failed to marshal searchSourceJSON: %v", err) + } + + savedObject["searchSourceJSON"] = string(searchSourceJSON) + } + } + + } + + path := "/" + imp.cfg.KibanaIndex + "/search/" + searchName + imp.statusMsg("Import search %s", file) + + if err = imp.client.LoadJSON(path, searchContent); err != nil { + return err + } + + return nil +} + +func (imp Importer) ImportIndex(file string) error { + + reader, err := ioutil.ReadFile(file) + if err != nil { + return err + } + var indexContent common.MapStr + json.Unmarshal(reader, &indexContent) + + indexName, ok := indexContent["title"].(string) + if !ok { + return errors.New(fmt.Sprintf("Missing title in the index-pattern file at %s", file)) + } + + if imp.cfg.Index != "" { + // change index pattern name + imp.statusMsg("Change index in index-pattern %s", indexName) + indexContent["title"] = imp.cfg.Index + } + + path := "/" + imp.cfg.KibanaIndex + "/index-pattern/" + indexName + imp.statusMsg("Import index to %s from %s\n", path, file) + + if err = imp.client.LoadJSON(path, indexContent); err != nil { + return err + } + return nil + +} + +func (imp Importer) ImportFile(fileType string, file string) error { + + if fileType == "dashboard" { + return imp.ImportDashboard(file) + } else if fileType == "index-pattern" { + return imp.ImportIndex(file) + } + return fmt.Errorf("Unexpected file type %s", fileType) +} + +func (imp Importer) ImportDir(dirType string, dir string) error { + + dir = path.Join(dir, dirType) + + imp.statusMsg("Import directory %s", dir) + errors := []string{} + + files, err := filepath.Glob(path.Join(dir, "*.json")) + if err != nil { + return fmt.Errorf("Failed to read directory %s. Error: %s", dir, err) + } + if len(files) == 0 { + return fmt.Errorf("The directory %s is empty, nothing to import", dir) + } + for _, file := range files { + + err = imp.ImportFile(dirType, file) + if err != nil { + errors = append(errors, fmt.Sprintf(" error loading %s: %s", file, err)) + } + } + if len(errors) > 0 { + return fmt.Errorf("Failed to load directory %s:\n%s", dir, strings.Join(errors, "\n")) + } + return nil + +} + +func (imp Importer) unzip(archive, target string) error { + + imp.statusMsg("Unzip archive %s", target) + + reader, err := zip.OpenReader(archive) + if err != nil { + return err + } + + for _, file := range reader.File { + filePath := filepath.Join(target, file.Name) + + if file.FileInfo().IsDir() { + os.MkdirAll(filePath, file.Mode()) + continue + } + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, fileReader); err != nil { + return err + } + } + return nil +} + +func (imp Importer) ImportArchive() error { + + var archive string + + target, err := ioutil.TempDir("", "tmp") + if err != nil { + return errors.New("Failed to generate a temporary directory name") + } + + if err = os.MkdirAll(target, 0755); err != nil { + return fmt.Errorf("Failed to create a temporary directory: %v", target) + } + + defer os.RemoveAll(target) // clean up + + imp.statusMsg("Create temporary directory %s", target) + if imp.cfg.File != "" { + archive = imp.cfg.File + } else if imp.cfg.Snapshot { + // In case snapshot is set, snapshot version is fetched + url := imp.cfg.SnapshotURL + archive, err = imp.downloadFile(url, target) + if err != nil { + return fmt.Errorf("Failed to download snapshot file: %s", url) + } + } else if imp.cfg.URL != "" { + archive, err = imp.downloadFile(imp.cfg.URL, target) + if err != nil { + return fmt.Errorf("Failed to download file: %s", imp.cfg.URL) + } + } else { + return errors.New("No archive file or URL is set - please use -file or -url option") + } + + err = imp.unzip(archive, target) + if err != nil { + return fmt.Errorf("Failed to unzip the archive: %s", archive) + } + dirs, err := getDirectories(target) + if err != nil { + return err + } + if len(dirs) != 1 { + return fmt.Errorf("Too many directories under %s", target) + } + + dirs, err = getDirectories(dirs[0]) + if err != nil { + return err + } + + for _, dir := range dirs { + imp.statusMsg("Importing Kibana from %s", dir) + if imp.cfg.Beat == "" || filepath.Base(dir) == imp.cfg.Beat { + err = imp.ImportKibana(dir) + if err != nil { + return err + } + } + } + return nil +} + +func getDirectories(target string) ([]string, error) { + + files, err := ioutil.ReadDir(target) + if err != nil { + return nil, err + } + var dirs []string + + for _, file := range files { + if file.IsDir() { + dirs = append(dirs, filepath.Join(target, file.Name())) + } + } + return dirs, nil +} + +func (imp Importer) downloadFile(url string, target string) (string, error) { + + fileName := filepath.Base(url) + targetPath := path.Join(target, fileName) + imp.statusMsg("Downloading %s", url) + + // Create the file + out, err := os.Create(targetPath) + if err != nil { + return targetPath, err + } + defer out.Close() + + // Get the data + resp, err := http.Get(url) + if err != nil { + return targetPath, err + } + defer resp.Body.Close() + + // Writer the body to file + _, err = io.Copy(out, resp.Body) + if err != nil { + return targetPath, err + } + + return targetPath, nil +} + +// import Kibana dashboards and index-pattern or only one of these +func (imp Importer) ImportKibana(dir string) error { + + var err error + + if _, err := os.Stat(dir); err != nil { + return fmt.Errorf("No directory %s", dir) + } + + check := []string{} + if !imp.cfg.OnlyDashboards { + check = append(check, "index-pattern") + } + if !imp.cfg.OnlyIndex { + check = append(check, "dashboard") + } + + types := []string{} + for _, c := range check { + if imp.subdirExists(dir, c) { + types = append(types, c) + } + } + + if len(types) == 0 { + return fmt.Errorf("The directory %s does not contain the %s subdirectory."+ + " There is nothing to import into Kibana.", dir, strings.Join(check, " or ")) + } + + for _, t := range types { + err = imp.ImportDir(t, dir) + if err != nil { + return fmt.Errorf("Failed to import %s: %v", t, err) + } + } + return nil +} + +func (imp Importer) subdirExists(parent string, child string) bool { + if _, err := os.Stat(path.Join(parent, child)); err != nil { + return false + } + return true +} diff --git a/libbeat/dashboards/dashboards/importer_integration_test.go b/libbeat/dashboards/dashboards/importer_integration_test.go new file mode 100644 index 00000000000..15938668ced --- /dev/null +++ b/libbeat/dashboards/dashboards/importer_integration_test.go @@ -0,0 +1,55 @@ +// +build integration + +package dashboards + +import ( + "testing" + + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/outputs/elasticsearch" + "github.com/stretchr/testify/assert" +) + +func TestImporter(t *testing.T) { + if testing.Verbose() { + logp.LogInit(logp.LOG_DEBUG, "", false, true, []string{"*"}) + } + + client := elasticsearch.GetTestingElasticsearch() + + imp, err := NewImporter(&DashboardsConfig{ + KibanaIndex: ".kibana-test", + File: "testdata/testbeat-dashboards.zip", + Beat: "testbeat", + }, client, nil) + + assert.NoError(t, err) + + err = imp.Import() + assert.NoError(t, err) + + status, _, _ := client.Request("GET", "/.kibana-test/dashboard/1e4389f0-e871-11e6-911d-3f8ed6f72700", "", nil, nil) + assert.Equal(t, 200, status) +} + +func TestImporterEmptyBeat(t *testing.T) { + if testing.Verbose() { + logp.LogInit(logp.LOG_DEBUG, "", false, true, []string{"*"}) + } + + client := elasticsearch.GetTestingElasticsearch() + + imp, err := NewImporter(&DashboardsConfig{ + KibanaIndex: ".kibana-test-nobeat", + File: "testdata/testbeat-dashboards.zip", + Beat: "", + }, client, nil) + + assert.NoError(t, err) + + err = imp.Import() + assert.NoError(t, err) + + status, _, _ := client.Request("GET", "/.kibana-test-nobeat/dashboard/1e4389f0-e871-11e6-911d-3f8ed6f72700", "", nil, nil) + assert.Equal(t, 200, status) +} diff --git a/libbeat/dashboards/dashboards/testdata/testbeat-dashboards.zip b/libbeat/dashboards/dashboards/testdata/testbeat-dashboards.zip new file mode 100644 index 0000000000000000000000000000000000000000..010cb96b7fb08ce413e8664f50faa497a969b562 GIT binary patch literal 3005 zcmWIWW@h1H00Gx5M=vk~N{BGXFqEVgmn5YomguG=7H1^oCl;j?>xYJLGB7h;niwGp z#HAJ742&#a85tN@M1cAN;HCv8qnaj*!!(!;5My5+CDB-QLdL=!PS667`#Bgu?zZT; zEtn7V(rPB4kBGOxFxAA^!ZOW3H`T)2P}k5f)lApY&@e^UIL#t8#VpO-$lSm{FRM5| z4;+FZuYxdI2-XIj&O2lvu;;tBlKg5HF9$Bo<#Ev~U9)xww)pJHjG5z}%g#0N|K4o% z%!h~0T5n!|v+)0e$|X;tCN35KHLurX{R8GHc`6DK0SglpBv!U$Kv*qT>_by4b%zEHEf8riN$w;?4H;cpWZii-^UFdO$ zVUnwei{O!i!6F+I;#3YiN-~UYNpv%~|FvqRv|;i?+-6C&Mv#_&N#L5rI-|}2xqD#v%10_^@IugcHETNxNeoG+w7h< zA(v0eJkNP+*L#KePtMw)Sz&9llm8wR*({rX$71!rzMfqYQ`<{9r{29GX??ShWhGnm zVn&M(wrexJ`VWNt)<5x8I-{$kU*%HsjVrmOSNA_~YTj}>@x$H5E$Or8Sua&xZ_my$ zyW9BQ*CVW_KkimomCF9FKGIF@B5(4xwg*0+6Y3aoCs-9i5nr5|Sd^SWLbCE&e(|;` zFj+|fla()#rl(mXrX;7Ng42`%xoN63(A)p80nfhA+N)%i`x?xUxRsf%awGB1wUwu& z)|)QrKl(0V_NV)WLM*!rSG;i+Gi+G&x}zN<>NJ#;fp-eB06S7mxmD7C>$^{JWA zDmS%{_fWFx=GIaDPd-+k)80{~qoIQ_fs=sd?bHy=rZLLfGzdji0O$ z-8G(*%@PB%6ywx13(Cr#)}VuVw+(pqe%EG_?|w7k-mJ?T z65Dy-M(OP2@l7-kN-mzN>c436vc~_lB5zF`i*NYWsK}pOUcO_NPnYgeR_0{G`6BJ1 z2aoWDAFT=1G5qMVN!Fr@dD(;Tqaj@1-cBfg5;##z$;8tjlRbO7qQ$bXq&`_c(cOo+ z_S!o7Jw84m! z9|d^+cmLNC+Wh#&=l^#Eb~XOr{Oa(9Em2GUZ)9C=`NBdY%j#kNzWLAdvd)@3@s{?n zj5DAAG<9=~Snf`-3w`QeBkR z(Zl|wKPTpEGqbI`$*|{wl#Nb8_qW8q?7a7to0srUno=TEJhMu0SB76oBv<$Ps@H8Y z>jZyX-g@uP_QN~mORl8Pd?H+1+Lm!WK&SccKi<=_me~q&`!}3d6N(FywzK`-`sPyn z*Z;r@nUP738CL}_0j=r<7~VR9n6SziwYq18RQG5V6|%{gl|8b_emG2q*#^uZKr67+ zh4`#cXF#@MDIqJ6n*sPN!_^3YSO(0S3`-i>fE3K_$RPz2(??iFLX!ZWt03`*9?B{x zLGT|~tH4}<)m8BN3ZG2~|D)Dc$o`K77Ei?3L_*z#&q>%luZ`k)Nmc?*LNAi>S%?S& j)B+hf4C+X;kc8qHm&;h$fQb*-K;>XK0Sy29>>wThJZJql literal 0 HcmV?d00001 diff --git a/libbeat/dashboards/import_dashboards.go b/libbeat/dashboards/import_dashboards.go index d509258d0b6..d81de51b8a9 100644 --- a/libbeat/dashboards/import_dashboards.go +++ b/libbeat/dashboards/import_dashboards.go @@ -1,23 +1,15 @@ package main import ( - "archive/zip" - "encoding/json" "errors" "flag" "fmt" - "io" - "io/ioutil" - "net/http" "os" - "path" - "path/filepath" - "strings" "time" lbeat "github.com/elastic/beats/libbeat/beat" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/common/fmtstr" + "github.com/elastic/beats/libbeat/dashboards/dashboards" "github.com/elastic/beats/libbeat/outputs" "github.com/elastic/beats/libbeat/outputs/elasticsearch" "github.com/elastic/beats/libbeat/outputs/outil" @@ -130,9 +122,7 @@ func (cl *CommandLine) ParseCommandLine() error { return nil } -func New() (*Importer, error) { - importer := Importer{} - +func New() (*dashboards.Importer, error) { /* define the command line arguments */ cl, err := DefineCommandLine() if err != nil { @@ -144,7 +134,20 @@ func New() (*Importer, error) { if err != nil { return nil, err } - importer.cl = cl + + cfg := dashboards.DashboardsConfig{ + Enabled: true, + KibanaIndex: cl.opt.KibanaIndex, + Index: cl.opt.Index, + Dir: cl.opt.Dir, + File: cl.opt.File, + Beat: cl.opt.Beat, + URL: cl.opt.URL, + OnlyDashboards: cl.opt.OnlyDashboards, + OnlyIndex: cl.opt.OnlyIndex, + Snapshot: cl.opt.Snapshot, + SnapshotURL: fmt.Sprintf("https://beats-nightlies.s3.amazonaws.com/dashboards/beats-dashboards-%s-SNAPSHOT.zip", lbeat.GetDefaultVersion()), + } /* prepare the Elasticsearch index pattern */ fmtstr, err := fmtstr.CompileEvent(cl.opt.Index) @@ -191,503 +194,20 @@ func New() (*Importer, error) { if err != nil { return nil, fmt.Errorf("Failed to connect to Elasticsearch: %s", err) } - importer.client = client - - return &importer, nil - -} - -func (imp Importer) statusMsg(msg string, a ...interface{}) { - if imp.cl.opt.Quiet { - return - } - - if len(a) == 0 { - fmt.Println(msg) - } else { - fmt.Println(fmt.Sprintf(msg, a...)) - } -} - -func (imp Importer) CreateIndex() error { - imp.client.CreateIndex(imp.cl.opt.KibanaIndex, nil) - _, _, err := imp.client.CreateIndex(imp.cl.opt.KibanaIndex+"/_mapping/search", - common.MapStr{ - "search": common.MapStr{ - "properties": common.MapStr{ - "hits": common.MapStr{ - "type": "integer", - }, - "version": common.MapStr{ - "type": "integer", - }, - }, - }, - }) - if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("Failed to set the mapping - %s", err)) - } - return nil -} - -func (imp Importer) ImportJSONFile(fileType string, file string) error { - - path := "/" + imp.cl.opt.KibanaIndex + "/" + fileType - - reader, err := ioutil.ReadFile(file) - if err != nil { - return fmt.Errorf("Failed to read %s. Error: %s", file, err) - } - var jsonContent map[string]interface{} - json.Unmarshal(reader, &jsonContent) - fileBase := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) - - err = imp.client.LoadJSON(path+"/"+fileBase, jsonContent) - if err != nil { - return fmt.Errorf("Failed to load %s under %s/%s: %s", file, path, fileBase, err) - } - - return nil -} - -func (imp Importer) ImportDashboard(file string) error { - - imp.statusMsg("Import dashboard %s", file) - - /* load dashboard */ - err := imp.ImportJSONFile("dashboard", file) - if err != nil { - return err - } - - /* load the visualizations and searches that depend on the dashboard */ - err = imp.importPanelsFromDashboard(file) - if err != nil { - return err - } - return nil -} - -func (imp Importer) importPanelsFromDashboard(file string) (err error) { - - // directory with the dashboards - dir := filepath.Dir(file) - - // main directory with dashboard, search, visualizations directories - mainDir := filepath.Dir(dir) - - reader, err := ioutil.ReadFile(file) - if err != nil { - return - } - type record struct { - Title string `json:"title"` - PanelsJSON string `json:"panelsJSON"` - } - type panel struct { - ID string `json:"id"` - Type string `json:"type"` - } - - var jsonContent record - json.Unmarshal(reader, &jsonContent) - - var widgets []panel - json.Unmarshal([]byte(jsonContent.PanelsJSON), &widgets) - - for _, widget := range widgets { - - if widget.Type == "visualization" { - err = imp.ImportVisualization(path.Join(mainDir, "visualization", widget.ID+".json")) - if err != nil { - return err - } - } else if widget.Type == "search" { - err = imp.ImportSearch(path.Join(mainDir, "search", widget.ID+".json")) - if err != nil { - return err - } - } else { - imp.statusMsg("Widgets: %v", widgets) - return fmt.Errorf("Unknown panel type %s in %s", widget.Type, file) + statusMsg := dashboards.MessageOutputter(func(msg string, a ...interface{}) { + if cl.opt.Quiet { + return } - } - return -} - -func (imp Importer) importSearchFromVisualization(file string) error { - type record struct { - Title string `json:"title"` - SavedSearchID string `json:"savedSearchId"` - } - - reader, err := ioutil.ReadFile(file) - if err != nil { - return nil - } - - var jsonContent record - json.Unmarshal(reader, &jsonContent) - id := jsonContent.SavedSearchID - if len(id) == 0 { - // no search used - return nil - } - - // directory with the visualizations - dir := filepath.Dir(file) - - // main directory - mainDir := filepath.Dir(dir) - - searchFile := path.Join(mainDir, "search", id+".json") - - if searchFile != "" { - // visualization depends on search - if err := imp.ImportSearch(searchFile); err != nil { - return err - } - } - return nil -} - -func (imp Importer) ImportVisualization(file string) error { - - imp.statusMsg("Import visualization %s", file) - if err := imp.ImportJSONFile("visualization", file); err != nil { - return err - } - - err := imp.importSearchFromVisualization(file) - if err != nil { - return err - } - return nil -} - -func (imp Importer) ImportSearch(file string) error { - - reader, err := ioutil.ReadFile(file) - if err != nil { - return err - } - searchName := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) - - var searchContent common.MapStr - err = json.Unmarshal(reader, &searchContent) - if err != nil { - return fmt.Errorf("Failed to unmarshal search content %s: %v", searchName, err) - } - - if imp.cl.opt.Index != "" { - - // change index pattern name - if savedObject, ok := searchContent["kibanaSavedObjectMeta"].(map[string]interface{}); ok { - if source, ok := savedObject["searchSourceJSON"].(string); ok { - var record common.MapStr - err = json.Unmarshal([]byte(source), &record) - if err != nil { - return fmt.Errorf("Failed to unmarshal searchSourceJSON from search %s: %v", searchName, err) - } - - if _, ok := record["index"]; ok { - record["index"] = imp.cl.opt.Index - } - searchSourceJSON, err := json.Marshal(record) - if err != nil { - return fmt.Errorf("Failed to marshal searchSourceJSON: %v", err) - } - - savedObject["searchSourceJSON"] = string(searchSourceJSON) - } - } - - } - - path := "/" + imp.cl.opt.KibanaIndex + "/search/" + searchName - imp.statusMsg("Import search %s", file) - - if err = imp.client.LoadJSON(path, searchContent); err != nil { - return err - } - - return nil -} - -func (imp Importer) ImportIndex(file string) error { - - reader, err := ioutil.ReadFile(file) - if err != nil { - return err - } - var indexContent common.MapStr - json.Unmarshal(reader, &indexContent) - - indexName, ok := indexContent["title"].(string) - if !ok { - return errors.New(fmt.Sprintf("Missing title in the index-pattern file at %s", file)) - } - - if imp.cl.opt.Index != "" { - // change index pattern name - imp.statusMsg("Change index in index-pattern %s", indexName) - indexContent["title"] = imp.cl.opt.Index - } - - path := "/" + imp.cl.opt.KibanaIndex + "/index-pattern/" + indexName - fmt.Printf("Import index to %s from %s\n", path, file) - - if err = imp.client.LoadJSON(path, indexContent); err != nil { - return err - } - return nil - -} - -func (imp Importer) ImportFile(fileType string, file string) error { - if fileType == "dashboard" { - return imp.ImportDashboard(file) - } else if fileType == "index-pattern" { - return imp.ImportIndex(file) - } - return fmt.Errorf("Unexpected file type %s", fileType) -} - -func (imp Importer) ImportDir(dirType string, dir string) error { - - dir = path.Join(dir, dirType) - - imp.statusMsg("Import directory %s", dir) - errors := []string{} - - files, err := filepath.Glob(path.Join(dir, "*.json")) - if err != nil { - return fmt.Errorf("Failed to read directory %s. Error: %s", dir, err) - } - if len(files) == 0 { - return fmt.Errorf("The directory %s is empty, nothing to import", dir) - } - for _, file := range files { - - err = imp.ImportFile(dirType, file) - if err != nil { - errors = append(errors, fmt.Sprintf(" error loading %s: %s", file, err)) - } - } - if len(errors) > 0 { - return fmt.Errorf("Failed to load directory %s:\n%s", dir, strings.Join(errors, "\n")) - } - return nil - -} - -func (imp Importer) unzip(archive, target string) error { - - imp.statusMsg("Unzip archive %s", target) - - reader, err := zip.OpenReader(archive) - if err != nil { - return err - } - - for _, file := range reader.File { - filePath := filepath.Join(target, file.Name) - - if file.FileInfo().IsDir() { - os.MkdirAll(filePath, file.Mode()) - continue - } - fileReader, err := file.Open() - if err != nil { - return err - } - defer fileReader.Close() - - targetFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) - if err != nil { - return err - } - defer targetFile.Close() - - if _, err := io.Copy(targetFile, fileReader); err != nil { - return err - } - } - return nil -} - -func getMainDir(target string) (string, error) { - - files, err := ioutil.ReadDir(target) - if err != nil { - return "", err - } - var dirs []string - - for _, file := range files { - if file.IsDir() { - dirs = append(dirs, file.Name()) - } - } - if len(dirs) != 1 { - return "", fmt.Errorf("Too many subdirectories under %s", target) - } - return filepath.Join(target, dirs[0]), nil -} - -func getDirectories(target string) ([]string, error) { - - files, err := ioutil.ReadDir(target) - if err != nil { - return nil, err - } - var dirs []string - - for _, file := range files { - if file.IsDir() { - dirs = append(dirs, filepath.Join(target, file.Name())) - } - } - return dirs, nil -} - -func (imp Importer) downloadFile(url string, target string) (string, error) { - - fileName := filepath.Base(url) - targetPath := path.Join(target, fileName) - imp.statusMsg("Downloading %s", url) - - // Create the file - out, err := os.Create(targetPath) - if err != nil { - return targetPath, err - } - defer out.Close() - - // Get the data - resp, err := http.Get(url) - if err != nil { - return targetPath, err - } - defer resp.Body.Close() - - // Writer the body to file - _, err = io.Copy(out, resp.Body) - if err != nil { - return targetPath, err - } - - return targetPath, nil -} - -func (imp Importer) ImportArchive() error { - - var archive string - - target, err := ioutil.TempDir("", "tmp") - if err != nil { - return errors.New("Failed to generate a temporary directory name") - } - - if err = os.MkdirAll(target, 0755); err != nil { - return fmt.Errorf("Failed to create a temporary directory: %v", target) - } - - defer os.RemoveAll(target) // clean up - - imp.statusMsg("Create temporary directory %s", target) - if imp.cl.opt.File != "" { - archive = imp.cl.opt.File - } else if imp.cl.opt.Snapshot { - // In case snapshot is set, snapshot version is fetched - url := fmt.Sprintf("https://beats-nightlies.s3.amazonaws.com/dashboards/beats-dashboards-%s-SNAPSHOT.zip", lbeat.GetDefaultVersion()) - archive, err = imp.downloadFile(url, target) - if err != nil { - return fmt.Errorf("Failed to download snapshot file: %s", url) - } - } else if imp.cl.opt.URL != "" { - archive, err = imp.downloadFile(imp.cl.opt.URL, target) - if err != nil { - return fmt.Errorf("Failed to download file: %s", imp.cl.opt.URL) - } - } else { - return errors.New("No archive file or URL is set - please use -file or -url option") - } - - err = imp.unzip(archive, target) - if err != nil { - return fmt.Errorf("Failed to unzip the archive: %s", archive) - } - dirs, err := getDirectories(target) - if err != nil { - return err - } - if len(dirs) != 1 { - return fmt.Errorf("Too many directories under %s", target) - } - - dirs, err = getDirectories(dirs[0]) - if err != nil { - return err - } - - for _, dir := range dirs { - imp.statusMsg("Importing Kibana from %s", dir) - if imp.cl.opt.Beat == "" || filepath.Base(dir) == imp.cl.opt.Beat { - err = imp.ImportKibana(dir) - if err != nil { - return err - } - } - } - return nil -} - -func (imp Importer) subdirExists(parent string, child string) bool { - if _, err := os.Stat(path.Join(parent, child)); err != nil { - return false - } - return true -} - -// import Kibana dashboards and index-pattern or only one of these -func (imp Importer) ImportKibana(dir string) error { - - var err error - - if _, err := os.Stat(dir); err != nil { - return fmt.Errorf("No directory %s", dir) - } - - check := []string{} - if !imp.cl.opt.OnlyDashboards { - check = append(check, "index-pattern") - } - if !imp.cl.opt.OnlyIndex { - check = append(check, "dashboard") - } - - types := []string{} - for _, c := range check { - if imp.subdirExists(dir, c) { - types = append(types, c) + if len(a) == 0 { + fmt.Println(msg) + } else { + fmt.Println(fmt.Sprintf(msg, a...)) } - } + }) - if len(types) == 0 { - return fmt.Errorf("The directory %s does not contain the %s subdirectory."+ - " There is nothing to import into Kibana.", dir, strings.Join(check, " or ")) - } - - for _, t := range types { - err = imp.ImportDir(t, dir) - if err != nil { - return fmt.Errorf("Failed to import %s: %v", t, err) - } - } - return nil + return dashboards.NewImporter(&cfg, client, &statusMsg) } func main() { @@ -698,25 +218,10 @@ func main() { fmt.Fprintln(os.Stderr, "Exiting") os.Exit(1) } - if err := importer.CreateIndex(); err != nil { + err = importer.Import() + if err != nil { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Exiting") os.Exit(1) } - - if importer.cl.opt.Dir != "" { - if err = importer.ImportKibana(importer.cl.opt.Dir); err != nil { - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, "Exiting") - os.Exit(1) - } - } else { - if importer.cl.opt.URL != "" || importer.cl.opt.File != "" { - if err = importer.ImportArchive(); err != nil { - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, "Exiting") - os.Exit(1) - } - } - } } diff --git a/libbeat/docs/dashboardsconfig.asciidoc b/libbeat/docs/dashboardsconfig.asciidoc new file mode 100644 index 00000000000..b894683007f --- /dev/null +++ b/libbeat/docs/dashboardsconfig.asciidoc @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////////// +//// This content is shared by all Elastic Beats. Make sure you keep the +//// descriptions here generic enough to work for all Beats that include +//// this file. When using cross references, make sure that the cross +//// references resolve correctly for any files that include this one. +//// Use the appropriate variables defined in the index.asciidoc file to +//// resolve Beat names: beatname_uc and beatname_lc +//// Use the following include to pull this content into a doc file: +//// include::../../libbeat/docs/dashboardsconfig.asciidoc[] +//// Make sure this content appears below a level 2 heading. +////////////////////////////////////////////////////////////////////////// + +[[configuration-dashboards]] +=== Dashboards Configuration + +beta[] + +The `dashboards` section of the +{beatname_lc}.yml+ config file contains options +for the automatic loading of the sample Beats dashboards. The loading of the +dashboards is disabled by default, but can be enabled either from the configuration +file or by using the `-setup` CLI flag. + +If dashboard loading is enabled, {beatname_uc} attempts to configure Kibana by +writing directly in the Elasticsearch index for the Kibana configuration (by +default, `.kibana`). To connect to Elasticsearch, it uses the settings defined +in the Eleasticsearch output. If the Elasticsearch output is not configured or +not enabled, {beatname_uc} will stop with an error. Loading the dashboards is +only attempted at the Beat start, if Elasticsearch is not available when the +Beat starts, {beatname_uc} will stop with an error. + +[source,yaml] +------------------------------------------------------------------------------ +dashboards.enabled: true +------------------------------------------------------------------------------ + +==== Dashboards Loading Options + +You can specify the following options in the `dashboards` section of the ++{beatname_lc}.yml+ config file: + +===== enabled + +If enabled, load the sample Kibana dashboards on startup. If no other options +are set, the dashboards archive is downloaded from the elastic.co website. + +===== url + +The URL from where to download the dashboards archive. By default this URL has a +value which is computed based on the Beat name and version. For released +versions, this URL points to the dashboard archive on the artifacts.elastic.co +website. + +===== directory + +The directory from where to read the dashboards. It is used instead of the URL +when it has a value. + +===== file + +The file archive (zip file) from where to read the dashboards. It is used +instead of the URL when it has a value. + +===== snapshot + +If this option is set to true, the snapshot URL is used instead of the default +URL. + +===== snapshot_url + +The URL from where to download the snapshot version of the dashboards. By +default this has a value which is computed based on the Beat name and version. + +===== beat + +In case the archive contains the dashboards from multiple Beats, this lets you +select which one to load. You can load all the dashboards in the archive by +setting this to the empty string. The default is "{beatname_lc}". + +===== kibana_index + +The name of the Kibana index to use for setting the configuration. Default is +".kibana" + +===== index + +The Elasticsearch index name. This overwrites the index name defined in the +dashboards and index pattern. Example: "testbeat-*" diff --git a/libbeat/docs/shared-command-line.asciidoc b/libbeat/docs/shared-command-line.asciidoc index 23a1ded6e0e..84304dab674 100644 --- a/libbeat/docs/shared-command-line.asciidoc +++ b/libbeat/docs/shared-command-line.asciidoc @@ -60,6 +60,10 @@ Set the default location for miscellaneous files. See the <> s *`-path.logs`*:: Set the default location for log files. See the <> section for details. +*`-setup`*:: +Load the sample Kibana dashboards. By default, this downloads an archive file containing the Beats dashboards +from the elastic.co website. See the <> section for more details and more options. + *`-v`*:: Enable verbose output to show INFO-level messages. diff --git a/metricbeat/docs/reference/configuration.asciidoc b/metricbeat/docs/reference/configuration.asciidoc index 0f4c26fe90b..56d88fabb33 100644 --- a/metricbeat/docs/reference/configuration.asciidoc +++ b/metricbeat/docs/reference/configuration.asciidoc @@ -18,6 +18,7 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> * <> +* <> * <> * <> diff --git a/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc b/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc index 1bbf6c2e4c5..3dce78cfd82 100644 --- a/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc +++ b/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc @@ -77,6 +77,9 @@ include::../../../../libbeat/docs/outputconfig.asciidoc[] pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] +pass::[] +include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] + pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 38cc00c9cc1..83081503769 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -841,6 +841,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: metricbeat + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, diff --git a/packetbeat/docs/reference/configuration.asciidoc b/packetbeat/docs/reference/configuration.asciidoc index db2c97cadab..709ed29c7b6 100644 --- a/packetbeat/docs/reference/configuration.asciidoc +++ b/packetbeat/docs/reference/configuration.asciidoc @@ -21,6 +21,7 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> * <> +* <> * <> * <> * <> diff --git a/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc b/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc index 7f90c349b69..f0a49bc6130 100644 --- a/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc +++ b/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc @@ -801,6 +801,9 @@ include::../../../../libbeat/docs/outputconfig.asciidoc[] pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] +pass::[] +include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] + pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] diff --git a/packetbeat/packetbeat.full.yml b/packetbeat/packetbeat.full.yml index 6e7f6e29ae0..35d8aa0960a 100644 --- a/packetbeat/packetbeat.full.yml +++ b/packetbeat/packetbeat.full.yml @@ -1001,6 +1001,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: packetbeat + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, diff --git a/winlogbeat/docs/reference/configuration.asciidoc b/winlogbeat/docs/reference/configuration.asciidoc index e100c0c9261..0335ee93974 100644 --- a/winlogbeat/docs/reference/configuration.asciidoc +++ b/winlogbeat/docs/reference/configuration.asciidoc @@ -19,6 +19,7 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> * <> +* <> * <> * <> diff --git a/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc b/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc index fbc7622e31f..b97b9b56074 100644 --- a/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc +++ b/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc @@ -358,6 +358,9 @@ include::../../../../libbeat/docs/outputconfig.asciidoc[] pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] +pass::[] +include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] + pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] diff --git a/winlogbeat/winlogbeat.full.yml b/winlogbeat/winlogbeat.full.yml index 38b9bd24e9f..9b43f04aa09 100644 --- a/winlogbeat/winlogbeat.full.yml +++ b/winlogbeat/winlogbeat.full.yml @@ -582,6 +582,45 @@ output.elasticsearch: # the default for the logs path is a logs subdirectory inside the home path. #path.logs: ${path.home}/logs +#============================== Dashboards ===================================== +# These settings control loading the sample dashboards to the Kibana index. Loading +# the dashboards is disabled by default and can be enabled either by setting the +# options here, or by using the `-setup` CLI flag. +#dashboards.enabled: false + +# The URL from where to download the dashboards archive. By default this URL +# has a value which is computed based on the Beat name and version. For released +# versions, this URL points to the dashboard archive on the artifacts.elastic.co +# website. +#dashboards.url: + +# The directory from where to read the dashboards. It is used instead of the URL +# when it has a value. +#dashboards.directory: + +# The file archive (zip file) from where to read the dashboards. It is used instead +# of the URL when it has a value. +#dashboards.file: + +# If this option is enabled, the snapshot URL is used instead of the default URL. +#dashboard.snapshot: false + +# The URL from where to download the snapshot version of the dashboards. By default +# this has a value which is computed based on the Beat name and version. +#dashboard.snapshot_url + +# In case the archive contains the dashboards from multiple Beats, this lets you +# select which one to load. You can load all the dashboards in the archive by +# setting this to the empty string. +#dashboard.beat: winlogbeat + +# The name of the Kibana index to use for setting the configuration. Default is ".kibana" +#dashboards.kibana_index: .kibana + +# The Elasticsearch index name. This overwrites the index name defined in the +# dashboards and index pattern. Example: testbeat-* +#dashboards.index: + #================================ Logging ====================================== # There are three options for the log output: syslog, file, stderr. # Under Windows systems, the log files are per default sent to the file output, From 3e77723f7c34f01092962fc33f0755d2ffa8341c Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Fri, 3 Feb 2017 09:46:23 +0100 Subject: [PATCH 50/78] Unify generator (#3452) * Simplify beat creation * Use one script create generic beat or metricbeat * Rename `generate` directory to `template` * Share most parts of the testing makefile * Update docs to new logic * Update travis build file * one more renaming --- .travis.yml | 4 +- CHANGELOG.asciidoc | 2 + Makefile | 8 ++- generate/beat.py | 7 --- generate/metricbeat.py | 7 --- generate/metricbeat/Makefile | 52 ------------------- {generate => generator}/Makefile | 0 {generate => generator}/beat/.gitignore | 0 {generate => generator}/beat/CHANGELOG.md | 0 generator/beat/Makefile | 3 ++ {generate => generator}/beat/README.md | 0 .../beat/{beat}/.gitignore | 0 .../beat/{beat}/.travis.yml | 0 .../beat/{beat}/CONTRIBUTING.md | 0 {generate => generator}/beat/{beat}/LICENSE | 0 {generate => generator}/beat/{beat}/Makefile | 0 {generate => generator}/beat/{beat}/NOTICE | 0 {generate => generator}/beat/{beat}/README.md | 0 .../beat/{beat}/_meta/beat.yml | 0 .../beat/{beat}/_meta/fields.yml | 0 .../beat/{beat}/beater/{beat}.go.tmpl | 0 .../beat/{beat}/config/config.go.tmpl | 0 .../beat/{beat}/config/config_test.go.tmpl | 0 .../beat/{beat}/docs/index.asciidoc | 0 .../beat/{beat}/main.go.tmpl | 0 .../beat/{beat}/main_test.go.tmpl | 0 .../{beat}/tests/system/config/{beat}.yml.j2 | 0 .../beat/{beat}/tests/system/requirements.txt | 0 .../beat/{beat}/tests/system/test_base.py | 0 .../beat/{beat}/tests/system/{beat}.py | 0 {generate/beat => generator/common}/Makefile | 24 +++++---- {generate => generator}/metricbeat/.gitignore | 0 .../metricbeat/CHANGELOG.md | 0 {generate => generator}/metricbeat/LICENSE | 0 generator/metricbeat/Makefile | 5 ++ {generate => generator}/metricbeat/README.md | 0 .../metricbeat/{beat}/.gitignore | 0 .../metricbeat/{beat}/CONTRIBUTING.md | 0 .../metricbeat/{beat}/LICENSE | 0 .../metricbeat/{beat}/Makefile | 0 .../metricbeat/{beat}/NOTICE | 0 .../metricbeat/{beat}/README.md | 0 .../metricbeat/{beat}/main.go.tmpl | 0 libbeat/docs/newbeat.asciidoc | 2 +- .../creating-beat-from-metricbeat.asciidoc | 2 +- {scripts => script}/clean_vendor.sh | 0 generate/helper.py => script/generate.py | 26 +++++++--- 47 files changed, 50 insertions(+), 92 deletions(-) delete mode 100644 generate/beat.py delete mode 100644 generate/metricbeat.py delete mode 100644 generate/metricbeat/Makefile rename {generate => generator}/Makefile (100%) rename {generate => generator}/beat/.gitignore (100%) rename {generate => generator}/beat/CHANGELOG.md (100%) create mode 100644 generator/beat/Makefile rename {generate => generator}/beat/README.md (100%) rename {generate => generator}/beat/{beat}/.gitignore (100%) rename {generate => generator}/beat/{beat}/.travis.yml (100%) rename {generate => generator}/beat/{beat}/CONTRIBUTING.md (100%) rename {generate => generator}/beat/{beat}/LICENSE (100%) rename {generate => generator}/beat/{beat}/Makefile (100%) rename {generate => generator}/beat/{beat}/NOTICE (100%) rename {generate => generator}/beat/{beat}/README.md (100%) rename {generate => generator}/beat/{beat}/_meta/beat.yml (100%) rename {generate => generator}/beat/{beat}/_meta/fields.yml (100%) rename {generate => generator}/beat/{beat}/beater/{beat}.go.tmpl (100%) rename {generate => generator}/beat/{beat}/config/config.go.tmpl (100%) rename {generate => generator}/beat/{beat}/config/config_test.go.tmpl (100%) rename {generate => generator}/beat/{beat}/docs/index.asciidoc (100%) rename {generate => generator}/beat/{beat}/main.go.tmpl (100%) rename {generate => generator}/beat/{beat}/main_test.go.tmpl (100%) rename {generate => generator}/beat/{beat}/tests/system/config/{beat}.yml.j2 (100%) rename {generate => generator}/beat/{beat}/tests/system/requirements.txt (100%) rename {generate => generator}/beat/{beat}/tests/system/test_base.py (100%) rename {generate => generator}/beat/{beat}/tests/system/{beat}.py (100%) rename {generate/beat => generator/common}/Makefile (79%) rename {generate => generator}/metricbeat/.gitignore (100%) rename {generate => generator}/metricbeat/CHANGELOG.md (100%) rename {generate => generator}/metricbeat/LICENSE (100%) create mode 100644 generator/metricbeat/Makefile rename {generate => generator}/metricbeat/README.md (100%) rename {generate => generator}/metricbeat/{beat}/.gitignore (100%) rename {generate => generator}/metricbeat/{beat}/CONTRIBUTING.md (100%) rename {generate => generator}/metricbeat/{beat}/LICENSE (100%) rename {generate => generator}/metricbeat/{beat}/Makefile (100%) rename {generate => generator}/metricbeat/{beat}/NOTICE (100%) rename {generate => generator}/metricbeat/{beat}/README.md (100%) rename {generate => generator}/metricbeat/{beat}/main.go.tmpl (100%) rename {scripts => script}/clean_vendor.sh (100%) rename generate/helper.py => script/generate.py (83%) diff --git a/.travis.yml b/.travis.yml index 55bf7809561..f875a84ccc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,10 +74,10 @@ matrix: # Generators - os: linux - env: TARGETS="-C generate/metricbeat test" + env: TARGETS="-C generator/metricbeat test" go: *go_version - os: linux - env: TARGETS="-C generate/beat test" + env: TARGETS="-C generator/beat test" go: *go_version addons: diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e8edbd73d80..06978e0c010 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -14,6 +14,8 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Affecting all Beats* +- Change beat generator. Use `$GOPATH/src/github.com/elastic/beats/script/generate.py` to generate a beat. {pull}3452[3452] + *Metricbeat* *Packetbeat* diff --git a/Makefile b/Makefile index ffc933f0db2..7535dc0e0c6 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ SNAPSHOT?=yes .PHONY: testsuite testsuite: $(foreach var,$(PROJECTS),$(MAKE) -C $(var) testsuite || exit 1;) - #$(MAKE) -C generate test + #$(MAKE) -C generator test stop-environments: $(foreach var,$(PROJECTS_ENV),$(MAKE) -C $(var) stop-environment || exit 0;) @@ -46,13 +46,13 @@ update: clean: rm -rf build $(foreach var,$(PROJECTS),$(MAKE) -C $(var) clean || exit 1;) - $(MAKE) -C generate clean + $(MAKE) -C generator clean # Cleans up the vendor directory from unnecessary files # This should always be run after updating the dependencies .PHONY: clean-vendor clean-vendor: - sh scripts/clean_vendor.sh + sh script/clean_vendor.sh .PHONY: check check: @@ -118,5 +118,3 @@ upload-release: .PHONY: notice notice: python dev-tools/generate_notice.py . - - diff --git a/generate/beat.py b/generate/beat.py deleted file mode 100644 index 7b75bfe6ad1..00000000000 --- a/generate/beat.py +++ /dev/null @@ -1,7 +0,0 @@ -import helper - -if __name__ == "__main__": - - parser = helper.get_parser() - args = parser.parse_args() - helper.generate_beat("beat", args) diff --git a/generate/metricbeat.py b/generate/metricbeat.py deleted file mode 100644 index b58cbf337e4..00000000000 --- a/generate/metricbeat.py +++ /dev/null @@ -1,7 +0,0 @@ -import helper - -if __name__ == "__main__": - - parser = helper.get_parser() - args = parser.parse_args() - helper.generate_beat("metricbeat", args) diff --git a/generate/metricbeat/Makefile b/generate/metricbeat/Makefile deleted file mode 100644 index 4cbc34dca64..00000000000 --- a/generate/metricbeat/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -BUILD_DIR?=build -PWD=$(shell pwd) -PYTHON_ENV?=${BUILD_DIR}/python-env -BEAT_PATH=${BUILD_DIR}/src/beatpath/testbeat - - - -# Runs test build for mock beat -.PHONY: test -test: python-env - - # Makes sure to use current version of beats for testing - mkdir -p ${BUILD_DIR}/src/github.com/elastic/beats/ - rsync -a --exclude=build ${PWD}/../../* ${BUILD_DIR}/src/github.com/elastic/beats/ - - mkdir -p ${BEAT_PATH} - export GOPATH=${PWD}/build ; \ - . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/generate/metricbeat.py --project_name=Testbeat --github_name=ruflin --beat_path=beatpath/testbeat --full_name="Nicolas Ruflin" - - . ${PYTHON_ENV}/bin/activate; \ - export GOPATH=${PWD}/build ; \ - export PATH=${PATH}:${PWD}/build/bin; \ - cd ${BEAT_PATH} ; \ - make copy-vendor ; \ - MODULE=elastic METRICSET=test make create-metricset ; \ - make check ; \ - make update ; \ - make ; \ - make unit - - -# Tests the build process for the beat -.PHONY: test-build -test-build: test - - # Copy dev tools - cp -r ${PWD}/../../../dev-tools ${BEAT_PATH}/vendor/github.com/elastic/beats/ - - cd ${BEAT_PATH}/dev-tools/packer ; \ - make deps ; \ - make images - -# Sets up the virtual python environment -.PHONY: python-env -python-env: - test -d ${PYTHON_ENV} || virtualenv ${PYTHON_ENV} - . ${PYTHON_ENV}/bin/activate && pip install --upgrade pip cookiecutter PyYAML - -# Cleans up environment -.PHONY: clean -clean: - rm -rf build diff --git a/generate/Makefile b/generator/Makefile similarity index 100% rename from generate/Makefile rename to generator/Makefile diff --git a/generate/beat/.gitignore b/generator/beat/.gitignore similarity index 100% rename from generate/beat/.gitignore rename to generator/beat/.gitignore diff --git a/generate/beat/CHANGELOG.md b/generator/beat/CHANGELOG.md similarity index 100% rename from generate/beat/CHANGELOG.md rename to generator/beat/CHANGELOG.md diff --git a/generator/beat/Makefile b/generator/beat/Makefile new file mode 100644 index 00000000000..4a4cc6232d5 --- /dev/null +++ b/generator/beat/Makefile @@ -0,0 +1,3 @@ + + +include ../common/Makefile diff --git a/generate/beat/README.md b/generator/beat/README.md similarity index 100% rename from generate/beat/README.md rename to generator/beat/README.md diff --git a/generate/beat/{beat}/.gitignore b/generator/beat/{beat}/.gitignore similarity index 100% rename from generate/beat/{beat}/.gitignore rename to generator/beat/{beat}/.gitignore diff --git a/generate/beat/{beat}/.travis.yml b/generator/beat/{beat}/.travis.yml similarity index 100% rename from generate/beat/{beat}/.travis.yml rename to generator/beat/{beat}/.travis.yml diff --git a/generate/beat/{beat}/CONTRIBUTING.md b/generator/beat/{beat}/CONTRIBUTING.md similarity index 100% rename from generate/beat/{beat}/CONTRIBUTING.md rename to generator/beat/{beat}/CONTRIBUTING.md diff --git a/generate/beat/{beat}/LICENSE b/generator/beat/{beat}/LICENSE similarity index 100% rename from generate/beat/{beat}/LICENSE rename to generator/beat/{beat}/LICENSE diff --git a/generate/beat/{beat}/Makefile b/generator/beat/{beat}/Makefile similarity index 100% rename from generate/beat/{beat}/Makefile rename to generator/beat/{beat}/Makefile diff --git a/generate/beat/{beat}/NOTICE b/generator/beat/{beat}/NOTICE similarity index 100% rename from generate/beat/{beat}/NOTICE rename to generator/beat/{beat}/NOTICE diff --git a/generate/beat/{beat}/README.md b/generator/beat/{beat}/README.md similarity index 100% rename from generate/beat/{beat}/README.md rename to generator/beat/{beat}/README.md diff --git a/generate/beat/{beat}/_meta/beat.yml b/generator/beat/{beat}/_meta/beat.yml similarity index 100% rename from generate/beat/{beat}/_meta/beat.yml rename to generator/beat/{beat}/_meta/beat.yml diff --git a/generate/beat/{beat}/_meta/fields.yml b/generator/beat/{beat}/_meta/fields.yml similarity index 100% rename from generate/beat/{beat}/_meta/fields.yml rename to generator/beat/{beat}/_meta/fields.yml diff --git a/generate/beat/{beat}/beater/{beat}.go.tmpl b/generator/beat/{beat}/beater/{beat}.go.tmpl similarity index 100% rename from generate/beat/{beat}/beater/{beat}.go.tmpl rename to generator/beat/{beat}/beater/{beat}.go.tmpl diff --git a/generate/beat/{beat}/config/config.go.tmpl b/generator/beat/{beat}/config/config.go.tmpl similarity index 100% rename from generate/beat/{beat}/config/config.go.tmpl rename to generator/beat/{beat}/config/config.go.tmpl diff --git a/generate/beat/{beat}/config/config_test.go.tmpl b/generator/beat/{beat}/config/config_test.go.tmpl similarity index 100% rename from generate/beat/{beat}/config/config_test.go.tmpl rename to generator/beat/{beat}/config/config_test.go.tmpl diff --git a/generate/beat/{beat}/docs/index.asciidoc b/generator/beat/{beat}/docs/index.asciidoc similarity index 100% rename from generate/beat/{beat}/docs/index.asciidoc rename to generator/beat/{beat}/docs/index.asciidoc diff --git a/generate/beat/{beat}/main.go.tmpl b/generator/beat/{beat}/main.go.tmpl similarity index 100% rename from generate/beat/{beat}/main.go.tmpl rename to generator/beat/{beat}/main.go.tmpl diff --git a/generate/beat/{beat}/main_test.go.tmpl b/generator/beat/{beat}/main_test.go.tmpl similarity index 100% rename from generate/beat/{beat}/main_test.go.tmpl rename to generator/beat/{beat}/main_test.go.tmpl diff --git a/generate/beat/{beat}/tests/system/config/{beat}.yml.j2 b/generator/beat/{beat}/tests/system/config/{beat}.yml.j2 similarity index 100% rename from generate/beat/{beat}/tests/system/config/{beat}.yml.j2 rename to generator/beat/{beat}/tests/system/config/{beat}.yml.j2 diff --git a/generate/beat/{beat}/tests/system/requirements.txt b/generator/beat/{beat}/tests/system/requirements.txt similarity index 100% rename from generate/beat/{beat}/tests/system/requirements.txt rename to generator/beat/{beat}/tests/system/requirements.txt diff --git a/generate/beat/{beat}/tests/system/test_base.py b/generator/beat/{beat}/tests/system/test_base.py similarity index 100% rename from generate/beat/{beat}/tests/system/test_base.py rename to generator/beat/{beat}/tests/system/test_base.py diff --git a/generate/beat/{beat}/tests/system/{beat}.py b/generator/beat/{beat}/tests/system/{beat}.py similarity index 100% rename from generate/beat/{beat}/tests/system/{beat}.py rename to generator/beat/{beat}/tests/system/{beat}.py diff --git a/generate/beat/Makefile b/generator/common/Makefile similarity index 79% rename from generate/beat/Makefile rename to generator/common/Makefile index 3d93de44d8e..c4157d9c92d 100644 --- a/generate/beat/Makefile +++ b/generator/common/Makefile @@ -1,31 +1,35 @@ BUILD_DIR?=build PWD=$(shell pwd) -PYTHON_ENV?=${BUILD_DIR}/python-env/ +PYTHON_ENV?=${BUILD_DIR}/python-env +BEAT_TYPE?=beat BEAT_PATH=${BUILD_DIR}/src/beatpath/testbeat ES_BEATS=${GOPATH}/src/github.com/elastic/beats +PREPARE_COMMAND?= # Runs test build for mock beat .PHONY: test -test: python-env - - # Makes sure to use current version of beats for testing - mkdir -p ${BUILD_DIR}/src/github.com/elastic/beats/ - rsync -a --exclude=build ${PWD}/../../* ${BUILD_DIR}/src/github.com/elastic/beats/ - - mkdir -p ${BEAT_PATH} - export GOPATH=${PWD}/build ; \ - . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/generate/beat.py --project_name=Testbeat --github_name=ruflin --beat_path=beatpath/testbeat --full_name="Nicolas Ruflin" +test: prepare-test . ${PYTHON_ENV}/bin/activate; \ export GOPATH=${PWD}/build ; \ export PATH=${PATH}:${PWD}/build/bin; \ cd ${BEAT_PATH} ; \ make copy-vendor ; \ + ${PREPARE_COMMAND} \ make check ; \ make update ; \ make ; \ make unit +prepare-test: python-env + # Makes sure to use current version of beats for testing + mkdir -p ${BUILD_DIR}/src/github.com/elastic/beats/ + rsync -a --exclude=build ${PWD}/../../* ${BUILD_DIR}/src/github.com/elastic/beats/ + + mkdir -p ${BEAT_PATH} + export GOPATH=${PWD}/build ; \ + . ${PYTHON_ENV}/bin/activate && python ${PWD}/build/src/github.com/elastic/beats/script/generate.py --type=${BEAT_TYPE} --project_name=Testbeat --github_name=ruflin --beat_path=beatpath/testbeat --full_name="Nicolas Ruflin" + # Runs test build for the created beat .PHONY: test-build test-build: test diff --git a/generate/metricbeat/.gitignore b/generator/metricbeat/.gitignore similarity index 100% rename from generate/metricbeat/.gitignore rename to generator/metricbeat/.gitignore diff --git a/generate/metricbeat/CHANGELOG.md b/generator/metricbeat/CHANGELOG.md similarity index 100% rename from generate/metricbeat/CHANGELOG.md rename to generator/metricbeat/CHANGELOG.md diff --git a/generate/metricbeat/LICENSE b/generator/metricbeat/LICENSE similarity index 100% rename from generate/metricbeat/LICENSE rename to generator/metricbeat/LICENSE diff --git a/generator/metricbeat/Makefile b/generator/metricbeat/Makefile new file mode 100644 index 00000000000..adc69e2948f --- /dev/null +++ b/generator/metricbeat/Makefile @@ -0,0 +1,5 @@ +BEAT_TYPE=metricbeat +PREPARE_COMMAND=MODULE=elastic METRICSET=test make create-metricset ; + +include ../common/Makefile + diff --git a/generate/metricbeat/README.md b/generator/metricbeat/README.md similarity index 100% rename from generate/metricbeat/README.md rename to generator/metricbeat/README.md diff --git a/generate/metricbeat/{beat}/.gitignore b/generator/metricbeat/{beat}/.gitignore similarity index 100% rename from generate/metricbeat/{beat}/.gitignore rename to generator/metricbeat/{beat}/.gitignore diff --git a/generate/metricbeat/{beat}/CONTRIBUTING.md b/generator/metricbeat/{beat}/CONTRIBUTING.md similarity index 100% rename from generate/metricbeat/{beat}/CONTRIBUTING.md rename to generator/metricbeat/{beat}/CONTRIBUTING.md diff --git a/generate/metricbeat/{beat}/LICENSE b/generator/metricbeat/{beat}/LICENSE similarity index 100% rename from generate/metricbeat/{beat}/LICENSE rename to generator/metricbeat/{beat}/LICENSE diff --git a/generate/metricbeat/{beat}/Makefile b/generator/metricbeat/{beat}/Makefile similarity index 100% rename from generate/metricbeat/{beat}/Makefile rename to generator/metricbeat/{beat}/Makefile diff --git a/generate/metricbeat/{beat}/NOTICE b/generator/metricbeat/{beat}/NOTICE similarity index 100% rename from generate/metricbeat/{beat}/NOTICE rename to generator/metricbeat/{beat}/NOTICE diff --git a/generate/metricbeat/{beat}/README.md b/generator/metricbeat/{beat}/README.md similarity index 100% rename from generate/metricbeat/{beat}/README.md rename to generator/metricbeat/{beat}/README.md diff --git a/generate/metricbeat/{beat}/main.go.tmpl b/generator/metricbeat/{beat}/main.go.tmpl similarity index 100% rename from generate/metricbeat/{beat}/main.go.tmpl rename to generator/metricbeat/{beat}/main.go.tmpl diff --git a/libbeat/docs/newbeat.asciidoc b/libbeat/docs/newbeat.asciidoc index 11126e52a32..85e570b2c42 100644 --- a/libbeat/docs/newbeat.asciidoc +++ b/libbeat/docs/newbeat.asciidoc @@ -142,7 +142,7 @@ Run python and specify the path to the Beat generator: [source,shell] -------------------- -python $GOPATH/src/github.com/elastic/beats/generate/beat.py +python $GOPATH/src/github.com/elastic/beats/script/generate.py -------------------- Python will prompt you to enter information about your Beat. For the `project_name`, enter `Countbeat`. diff --git a/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc b/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc index ce9327ff7a5..c35fd6db805 100644 --- a/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc +++ b/metricbeat/docs/developer-guide/creating-beat-from-metricbeat.asciidoc @@ -47,7 +47,7 @@ Run the command: [source,bash] ---- -python ${GOPATH}/src/github.com/elastic/beats/generate/metricbeat.py +python ${GOPATH}/src/github.com/elastic/beats/script/generate.py --type=metricbeat ---- When prompted, enter the Beat name and path. diff --git a/scripts/clean_vendor.sh b/script/clean_vendor.sh similarity index 100% rename from scripts/clean_vendor.sh rename to script/clean_vendor.sh diff --git a/generate/helper.py b/script/generate.py similarity index 83% rename from generate/helper.py rename to script/generate.py index 6ea5291cc74..9611011f2c4 100644 --- a/generate/helper.py +++ b/script/generate.py @@ -1,7 +1,7 @@ import os import argparse -# Creates a new beat based on the given parameters +# Creates a new beat or metricbeat based on the given parameters project_name = "" github_name = "" @@ -10,7 +10,8 @@ full_name = "" -def generate_beat(template_path, args): +def generate_beat(args): + global project_name, github_name, beat, beat_path, full_name @@ -27,7 +28,7 @@ def generate_beat(template_path, args): full_name = args.full_name read_input() - process_file(template_path) + process_file(args.type) def read_input(): @@ -50,13 +51,13 @@ def read_input(): full_name = raw_input("Firstname Lastname: ") or "Firstname Lastname" -def process_file(template_path): +def process_file(beat_type): # Load path information - generator_path = os.path.dirname(os.path.realpath(__file__)) + template_path = os.path.dirname(os.path.realpath(__file__)) + '/../generator' go_path = os.environ['GOPATH'] - for root, dirs, files in os.walk(generator_path + '/' + template_path + '/{beat}'): + for root, dirs, files in os.walk(template_path + '/' + beat_type + '/{beat}'): for file in files: @@ -74,7 +75,7 @@ def process_file(template_path): new_path = replace_variables(full_path).replace(".go.tmpl", ".go") # remove generator info and beat name from path - file_path = new_path.replace(generator_path + "/" + template_path + "/" + beat, "") + file_path = new_path.replace(template_path + "/" + beat_type + "/" + beat, "") # New file path to write file content to write_file = go_path + "/src/" + beat_path + "/" + file_path @@ -108,5 +109,16 @@ def get_parser(): parser.add_argument("--github_name", help="Github name") parser.add_argument("--beat_path", help="Beat path") parser.add_argument("--full_name", help="Full name") + parser.add_argument("--type", help="Beat type", default="beat") return parser + + +if __name__ == "__main__": + + parser = get_parser() + args = parser.parse_args() + + generate_beat(args) + + From 15db49f2650bdf9b9b6723d49fa3449698f65ae7 Mon Sep 17 00:00:00 2001 From: Douae Jeouit Date: Fri, 3 Feb 2017 11:53:17 +0100 Subject: [PATCH 51/78] Metricbeat : new metricset image for docker module (#3467) --- CHANGELOG.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 61 ++++++++++++++++++ metricbeat/docs/modules/docker.asciidoc | 4 ++ metricbeat/docs/modules/docker/image.asciidoc | 19 ++++++ metricbeat/include/list.go | 1 + metricbeat/metricbeat.template-es2x.json | 31 +++++++++ metricbeat/metricbeat.template.json | 29 +++++++++ .../module/docker/image/_meta/data.json | 19 ++++++ .../module/docker/image/_meta/docs.asciidoc | 3 + .../module/docker/image/_meta/fields.yml | 41 ++++++++++++ metricbeat/module/docker/image/data.go | 37 +++++++++++ metricbeat/module/docker/image/image.go | 63 +++++++++++++++++++ .../docker/image/image_integration_test.go | 23 +++++++ metricbeat/tests/system/test_docker.py | 31 +++++++++ 14 files changed, 363 insertions(+) create mode 100644 metricbeat/docs/modules/docker/image.asciidoc create mode 100644 metricbeat/module/docker/image/_meta/data.json create mode 100644 metricbeat/module/docker/image/_meta/docs.asciidoc create mode 100644 metricbeat/module/docker/image/_meta/fields.yml create mode 100644 metricbeat/module/docker/image/data.go create mode 100644 metricbeat/module/docker/image/image.go create mode 100644 metricbeat/module/docker/image/image_integration_test.go diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 06978e0c010..10dce15ced1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -92,6 +92,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Kafka consumer groups metricset. {pull}3240[3240] - Add dynamic configuration reloading for modules. {pull}3281[3281] - Add docker health metricset {pull}3357[3357] +- Add docker image metricset {pull}3467[3467] - System module uses new matchers for white-listing processes. {pull}3469[3469] - Add CEPH module with health metricset. {pull}3311[3311] - Add php_fpm module with pool metricset. {pull}3415[3415] diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index d1121ca37c9..a80b8042c0a 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1399,6 +1399,67 @@ type: integer Healthcheck status code +[float] +== image Fields + +Docker image metrics. + + + +[float] +== id Fields + +The image layers identifier. + + + +[float] +=== docker.image.id.current + +type: keyword + +Unique image identifier given upon its creation. + + +[float] +=== docker.image.id.parent + +type: keyword + +Identifier of the image, if it exists, from which the current image directly descends. + + +[float] +=== docker.image.created + +type: date + +Date and time when the image was created. + + +[float] +== size Fields + +Image size layers. + + + +[float] +=== docker.image.size.virtual + +type: long + +Size of the image. + + +[float] +=== docker.image.size.regular + +type: long + +Total size of the all cached images associated to the current image. + + [float] == info Fields diff --git a/metricbeat/docs/modules/docker.asciidoc b/metricbeat/docs/modules/docker.asciidoc index 2388f6ea9e0..2d11ff9b87b 100644 --- a/metricbeat/docs/modules/docker.asciidoc +++ b/metricbeat/docs/modules/docker.asciidoc @@ -46,6 +46,8 @@ The following metricsets are available: * <> +* <> + * <> * <> @@ -60,6 +62,8 @@ include::docker/diskio.asciidoc[] include::docker/healthcheck.asciidoc[] +include::docker/image.asciidoc[] + include::docker/info.asciidoc[] include::docker/memory.asciidoc[] diff --git a/metricbeat/docs/modules/docker/image.asciidoc b/metricbeat/docs/modules/docker/image.asciidoc new file mode 100644 index 00000000000..2771f927332 --- /dev/null +++ b/metricbeat/docs/modules/docker/image.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-docker-image]] +include::../../../module/docker/image/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/docker/image/_meta/data.json[] +---- diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index 4e3b8d4a658..c80c67e525c 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -22,6 +22,7 @@ import ( _ "github.com/elastic/beats/metricbeat/module/docker/cpu" _ "github.com/elastic/beats/metricbeat/module/docker/diskio" _ "github.com/elastic/beats/metricbeat/module/docker/healthcheck" + _ "github.com/elastic/beats/metricbeat/module/docker/image" _ "github.com/elastic/beats/metricbeat/module/docker/info" _ "github.com/elastic/beats/metricbeat/module/docker/memory" _ "github.com/elastic/beats/metricbeat/module/docker/network" diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 83a3fa5c6cc..e49291cbc95 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -815,6 +815,37 @@ } } }, + "image": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "properties": { + "current": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "parent": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + }, + "size": { + "properties": { + "regular": { + "type": "long" + }, + "virtual": { + "type": "long" + } + } + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index f317f31acd3..8286409179e 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -811,6 +811,35 @@ } } }, + "image": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "properties": { + "current": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "properties": { + "regular": { + "type": "long" + }, + "virtual": { + "type": "long" + } + } + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/module/docker/image/_meta/data.json b/metricbeat/module/docker/image/_meta/data.json new file mode 100644 index 00000000000..0c3fadc2f75 --- /dev/null +++ b/metricbeat/module/docker/image/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"docker", + "name":"image", + "rtt":44269 + }, + "docker":{ + "image":{ + "example": "image" + } + }, + "type":"metricsets" +} diff --git a/metricbeat/module/docker/image/_meta/docs.asciidoc b/metricbeat/module/docker/image/_meta/docs.asciidoc new file mode 100644 index 00000000000..d78ba7a3d22 --- /dev/null +++ b/metricbeat/module/docker/image/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== docker image MetricSet + +This is the image metricset of the module docker. diff --git a/metricbeat/module/docker/image/_meta/fields.yml b/metricbeat/module/docker/image/_meta/fields.yml new file mode 100644 index 00000000000..c4bb774a38f --- /dev/null +++ b/metricbeat/module/docker/image/_meta/fields.yml @@ -0,0 +1,41 @@ +- name: image + type: group + description: > + Docker image metrics. + fields: + - name: id + type: group + description: > + The image layers identifier. + fields: + - name: current + type: keyword + description: > + Unique image identifier given upon its creation. + - name: parent + type: keyword + description: > + Identifier of the image, if it exists, from which the current image directly descends. + - name: created + type: date + description: > + Date and time when the image was created. + - name: size + type: group + description: > + Image size layers. + fields: + - name: virtual + type: long + description: > + Size of the image. + - name: regular + type: long + description: > + Total size of the all cached images associated to the current image. + +# TODO : How to describe tags & labels list ? +# - name: tags +# type: list ? +# description: > +# Descriptive or given name(s) to the image. diff --git a/metricbeat/module/docker/image/data.go b/metricbeat/module/docker/image/data.go new file mode 100644 index 00000000000..877a3d7db63 --- /dev/null +++ b/metricbeat/module/docker/image/data.go @@ -0,0 +1,37 @@ +package image + +import ( + "github.com/elastic/beats/libbeat/common" + + "github.com/elastic/beats/metricbeat/module/docker" + dc "github.com/fsouza/go-dockerclient" + "time" +) + +func eventsMapping(imagesList []dc.APIImages) []common.MapStr { + events := []common.MapStr{} + for _, image := range imagesList { + events = append(events, eventMapping(&image)) + } + return events +} + +func eventMapping(image *dc.APIImages) common.MapStr { + event := common.MapStr{ + "id": common.MapStr{ + "current": image.ID, + "parent": image.ParentID, + }, + "created": common.Time(time.Unix(image.Created, 0)), + "size": common.MapStr{ + "regular": image.Size, + "virtual": image.VirtualSize, + }, + "tags": image.RepoTags, + } + labels := docker.DeDotLabels(image.Labels) + if len(labels) > 0 { + event["labels"] = labels + } + return event +} diff --git a/metricbeat/module/docker/image/image.go b/metricbeat/module/docker/image/image.go new file mode 100644 index 00000000000..ebac4544192 --- /dev/null +++ b/metricbeat/module/docker/image/image.go @@ -0,0 +1,63 @@ +package image + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/module/docker" + + dc "github.com/fsouza/go-dockerclient" +) + +// init registers the MetricSet with the central registry. +// The New method will be called after the setup of the module and before starting to fetch data +func init() { + if err := mb.Registry.AddMetricSet("docker", "image", New); err != nil { + panic(err) + } +} + +// MetricSet type defines all fields of the MetricSet +// As a minimum it must inherit the mb.BaseMetricSet fields, but can be extended with +// additional entries. These variables can be used to persist data or configuration between +// multiple fetch calls. +type MetricSet struct { + mb.BaseMetricSet + dockerClient *dc.Client +} + +// New create a new instance of the MetricSet +// Part of new is also setting up the configuration by processing additional +// configuration entries if needed. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + + logp.Warn("EXPERIMENTAL: The docker info metricset is experimental") + + config := docker.Config{} + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + client, err := docker.NewDockerClient(base.HostData().URI, config) + if err != nil { + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + dockerClient: client, + }, nil +} + +// Fetch methods implements the data gathering and data conversion to the right format +// It returns the event which is then forward to the output. In case of an error, a +// descriptive error must be returned. +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + + images, err := m.dockerClient.ListImages(dc.ListImagesOptions{}) + if err != nil { + return nil, err + } + + return eventsMapping(images), nil +} diff --git a/metricbeat/module/docker/image/image_integration_test.go b/metricbeat/module/docker/image/image_integration_test.go new file mode 100644 index 00000000000..988927ab852 --- /dev/null +++ b/metricbeat/module/docker/image/image_integration_test.go @@ -0,0 +1,23 @@ +package image + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "docker", + "metricsets": []string{"image"}, + "hosts": []string{"unix:///var/run/docker.sock"}, + } +} diff --git a/metricbeat/tests/system/test_docker.py b/metricbeat/tests/system/test_docker.py index 1aaff6c5b75..be883f32203 100644 --- a/metricbeat/tests/system/test_docker.py +++ b/metricbeat/tests/system/test_docker.py @@ -192,6 +192,37 @@ def test_health_fields(self): evt = self.remove_labels(evt) self.assert_fields_are_documented(evt) + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_image_fields(self): + """ + test image fields + """ + self.render_config_template(modules=[{ + "name": "docker", + "metricsets": ["image"], + "hosts": ["unix:///var/run/docker.sock"], + "period": "1s", + }]) + + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + # Ensure no errors or warnings exist in the log. + log = self.get_log() + self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + + output = self.read_output_json() + evt = output[0] + + if 'tags' in evt["docker"]["image"] : + del evt["docker"]["image"]["tags"] + + if 'labels' in evt["docker"]["image"] : + del evt["docker"]["image"]["labels"] + + self.assert_fields_are_documented(evt) + def remove_labels(self, evt): if 'labels' in evt["docker"]["container"]: From 65ead9d713ba2cd86372187c67fd2457a378c5bd Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Fri, 3 Feb 2017 12:59:51 +0100 Subject: [PATCH 52/78] Check if the pipeline exists before loading (#3522) This is probably safer since if one has lots and lots of Beats this is less likely to block the Ingest node processing. Part of #3159. --- filebeat/fileset/modules.go | 6 ++++++ filebeat/fileset/modules_integration_test.go | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/filebeat/fileset/modules.go b/filebeat/fileset/modules.go index 2bdc3631e31..6829d0e64fe 100644 --- a/filebeat/fileset/modules.go +++ b/filebeat/fileset/modules.go @@ -245,6 +245,7 @@ func (reg *ModuleRegistry) GetProspectorConfigs() ([]*common.Config, error) { // the pipelines. type PipelineLoader interface { LoadJSON(path string, json map[string]interface{}) error + Request(method, path string, pipeline string, params map[string]string, body interface{}) (int, []byte, error) } // LoadPipelines loads the pipelines for each configured fileset. @@ -266,6 +267,11 @@ func (reg *ModuleRegistry) LoadPipelines(esClient PipelineLoader) error { func loadPipeline(esClient PipelineLoader, pipelineID string, content map[string]interface{}) error { path := "/_ingest/pipeline/" + pipelineID + status, _, _ := esClient.Request("GET", path, "", nil, nil) + if status == 200 { + logp.Debug("modules", "Pipeline %s already loaded", pipelineID) + return nil + } err := esClient.LoadJSON(path, content) if err != nil { return fmt.Errorf("couldn't load template: %v", err) diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index acfc98d7b90..b9400844fae 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -3,6 +3,7 @@ package fileset import ( + "encoding/json" "path/filepath" "testing" @@ -29,8 +30,23 @@ func TestLoadPipeline(t *testing.T) { err := loadPipeline(client, "my-pipeline-id", content) assert.NoError(t, err) - status, _, _ := client.Request("GET", "/_ingest/pipeline/my-pipeline-id", "", nil, nil) + status, _, err := client.Request("GET", "/_ingest/pipeline/my-pipeline-id", "", nil, nil) + assert.NoError(t, err) + assert.Equal(t, 200, status) + + // loading again shouldn't actually update the pipeline + content["description"] = "describe pipeline 2" + err = loadPipeline(client, "my-pipeline-id", content) + assert.NoError(t, err) + + status, response, err := client.Request("GET", "/_ingest/pipeline/my-pipeline-id", "", nil, nil) + assert.NoError(t, err) assert.Equal(t, 200, status) + + var res map[string]interface{} + err = json.Unmarshal(response, &res) + assert.NoError(t, err) + assert.Equal(t, "describe pipeline", res["my-pipeline-id"].(map[string]interface{})["description"], string(response)) } func TestSetupNginx(t *testing.T) { From 42686b0a40e0563c77e5b3b08a9a2d1d499d10f0 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Fri, 3 Feb 2017 14:43:17 +0100 Subject: [PATCH 53/78] Use the Beat version in the Ingest Node pipeline (#3516) This adds the Beat version to the pipeline ID, which means that if we change the pipeline between versions, the new version will be used automatically. It also means that one can run different versions of the same Beat and the pipelines won't override each other. The pipelines are loaded automatically on the Beat start. Part of #3159. --- filebeat/beater/filebeat.go | 2 +- filebeat/fileset/fileset.go | 14 +++++++------- filebeat/fileset/fileset_test.go | 12 ++++++------ filebeat/fileset/modules.go | 9 +++++---- filebeat/fileset/modules_integration_test.go | 10 +++++----- filebeat/fileset/modules_test.go | 6 +++--- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/filebeat/beater/filebeat.go b/filebeat/beater/filebeat.go index 8c2c6a5d03d..b33b6cf9efe 100644 --- a/filebeat/beater/filebeat.go +++ b/filebeat/beater/filebeat.go @@ -37,7 +37,7 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) { return nil, fmt.Errorf("Error reading config file: %v", err) } - moduleRegistry, err := fileset.NewModuleRegistry(config.Modules) + moduleRegistry, err := fileset.NewModuleRegistry(config.Modules, b.Version) if err != nil { return nil, err } diff --git a/filebeat/fileset/fileset.go b/filebeat/fileset/fileset.go index e723dd3ed76..0938399aeb6 100644 --- a/filebeat/fileset/fileset.go +++ b/filebeat/fileset/fileset.go @@ -51,7 +51,7 @@ func New( } // Read reads the manifest file and evaluates the variables. -func (fs *Fileset) Read() error { +func (fs *Fileset) Read(beatVersion string) error { var err error fs.manifest, err = fs.readManifest() if err != nil { @@ -63,7 +63,7 @@ func (fs *Fileset) Read() error { return err } - fs.pipelineID, err = fs.getPipelineID() + fs.pipelineID, err = fs.getPipelineID(beatVersion) if err != nil { return err } @@ -241,13 +241,13 @@ func (fs *Fileset) getProspectorConfig() (*common.Config, error) { } // getPipelineID returns the Ingest Node pipeline ID -func (fs *Fileset) getPipelineID() (string, error) { +func (fs *Fileset) getPipelineID(beatVersion string) (string, error) { path, err := applyTemplate(fs.vars, fs.manifest.IngestPipeline) if err != nil { return "", fmt.Errorf("Error expanding vars on the ingest pipeline path: %v", err) } - return formatPipelineID(fs.mcfg.Module, fs.name, path), nil + return formatPipelineID(fs.mcfg.Module, fs.name, path, beatVersion), nil } func (fs *Fileset) GetPipeline() (pipelineID string, content map[string]interface{}, err error) { @@ -266,12 +266,12 @@ func (fs *Fileset) GetPipeline() (pipelineID string, content map[string]interfac if err != nil { return "", nil, fmt.Errorf("Error JSON decoding the pipeline file: %s: %v", path, err) } - return formatPipelineID(fs.mcfg.Module, fs.name, path), content, nil + return fs.pipelineID, content, nil } // formatPipelineID generates the ID to be used for the pipeline ID in Elasticsearch -func formatPipelineID(module, fileset, path string) string { - return fmt.Sprintf("%s-%s-%s", module, fileset, removeExt(filepath.Base(path))) +func formatPipelineID(module, fileset, path, beatVersion string) string { + return fmt.Sprintf("filebeat-%s-%s-%s-%s", beatVersion, module, fileset, removeExt(filepath.Base(path))) } // removeExt returns the file name without the extension. If no dot is found, diff --git a/filebeat/fileset/fileset_test.go b/filebeat/fileset/fileset_test.go index 6c8bfbf5701..8ee77050264 100644 --- a/filebeat/fileset/fileset_test.go +++ b/filebeat/fileset/fileset_test.go @@ -149,7 +149,7 @@ func TestResolveVariable(t *testing.T) { func TestGetProspectorConfigNginx(t *testing.T) { fs := getModuleForTesting(t, "nginx", "access") - assert.NoError(t, fs.Read()) + assert.NoError(t, fs.Read("5.2.0")) cfg, err := fs.getProspectorConfig() assert.NoError(t, err) @@ -159,7 +159,7 @@ func TestGetProspectorConfigNginx(t *testing.T) { assert.True(t, cfg.HasField("pipeline")) pipelineID, err := cfg.String("pipeline", -1) assert.NoError(t, err) - assert.Equal(t, "nginx-access-with_plugins", pipelineID) + assert.Equal(t, "filebeat-5.2.0-nginx-access-with_plugins", pipelineID) } func TestGetProspectorConfigNginxOverrides(t *testing.T) { @@ -172,7 +172,7 @@ func TestGetProspectorConfigNginxOverrides(t *testing.T) { }) assert.NoError(t, err) - assert.NoError(t, fs.Read()) + assert.NoError(t, fs.Read("5.2.0")) cfg, err := fs.getProspectorConfig() assert.NoError(t, err) @@ -183,17 +183,17 @@ func TestGetProspectorConfigNginxOverrides(t *testing.T) { assert.True(t, cfg.HasField("pipeline")) pipelineID, err := cfg.String("pipeline", -1) assert.NoError(t, err) - assert.Equal(t, "nginx-access-with_plugins", pipelineID) + assert.Equal(t, "filebeat-5.2.0-nginx-access-with_plugins", pipelineID) } func TestGetPipelineNginx(t *testing.T) { fs := getModuleForTesting(t, "nginx", "access") - assert.NoError(t, fs.Read()) + assert.NoError(t, fs.Read("5.2.0")) pipelineID, content, err := fs.GetPipeline() assert.NoError(t, err) - assert.Equal(t, "nginx-access-with_plugins", pipelineID) + assert.Equal(t, "filebeat-5.2.0-nginx-access-with_plugins", pipelineID) assert.Contains(t, content, "description") assert.Contains(t, content, "processors") } diff --git a/filebeat/fileset/modules.go b/filebeat/fileset/modules.go index 6829d0e64fe..7845ee99f96 100644 --- a/filebeat/fileset/modules.go +++ b/filebeat/fileset/modules.go @@ -18,7 +18,8 @@ type ModuleRegistry struct { // newModuleRegistry reads and loads the configured module into the registry. func newModuleRegistry(modulesPath string, moduleConfigs []ModuleConfig, - overrides *ModuleOverrides) (*ModuleRegistry, error) { + overrides *ModuleOverrides, + beatVersion string) (*ModuleRegistry, error) { var reg ModuleRegistry reg.registry = map[string]map[string]*Fileset{} @@ -53,7 +54,7 @@ func newModuleRegistry(modulesPath string, if err != nil { return nil, err } - err = fileset.Read() + err = fileset.Read(beatVersion) if err != nil { return nil, fmt.Errorf("Error reading fileset %s/%s: %v", mcfg.Module, filesetName, err) } @@ -81,7 +82,7 @@ func newModuleRegistry(modulesPath string, } // NewModuleRegistry reads and loads the configured module into the registry. -func NewModuleRegistry(moduleConfigs []*common.Config) (*ModuleRegistry, error) { +func NewModuleRegistry(moduleConfigs []*common.Config, beatVersion string) (*ModuleRegistry, error) { modulesPath := paths.Resolve(paths.Home, "module") stat, err := os.Stat(modulesPath) @@ -106,7 +107,7 @@ func NewModuleRegistry(moduleConfigs []*common.Config) (*ModuleRegistry, error) if err != nil { return nil, err } - return newModuleRegistry(modulesPath, mcfgs, modulesOverrides) + return newModuleRegistry(modulesPath, mcfgs, modulesOverrides, beatVersion) } func mcfgFromConfig(cfg *common.Config) (*ModuleConfig, error) { diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index b9400844fae..8e98a2c848d 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -51,8 +51,8 @@ func TestLoadPipeline(t *testing.T) { func TestSetupNginx(t *testing.T) { client := elasticsearch.GetTestingElasticsearch() - client.Request("DELETE", "/_ingest/pipeline/nginx-access-with_plugins", "", nil, nil) - client.Request("DELETE", "/_ingest/pipeline/nginx-error-pipeline", "", nil, nil) + client.Request("DELETE", "/_ingest/pipeline/filebeat-5.2.0-nginx-access-with_plugins", "", nil, nil) + client.Request("DELETE", "/_ingest/pipeline/filebeat-5.2.0-nginx-error-pipeline", "", nil, nil) modulesPath, err := filepath.Abs("../module") assert.NoError(t, err) @@ -61,14 +61,14 @@ func TestSetupNginx(t *testing.T) { {Module: "nginx"}, } - reg, err := newModuleRegistry(modulesPath, configs, nil) + reg, err := newModuleRegistry(modulesPath, configs, nil, "5.2.0") assert.NoError(t, err) err = reg.LoadPipelines(client) assert.NoError(t, err) - status, _, _ := client.Request("GET", "/_ingest/pipeline/nginx-access-with_plugins", "", nil, nil) + status, _, _ := client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-access-with_plugins", "", nil, nil) assert.Equal(t, 200, status) - status, _, _ = client.Request("GET", "/_ingest/pipeline/nginx-error-pipeline", "", nil, nil) + status, _, _ = client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-error-pipeline", "", nil, nil) assert.Equal(t, 200, status) } diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index a6f5a612ea1..db9169e5bc7 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -30,7 +30,7 @@ func TestNewModuleRegistry(t *testing.T) { {Module: "system"}, } - reg, err := newModuleRegistry(modulesPath, configs, nil) + reg, err := newModuleRegistry(modulesPath, configs, nil, "5.2.0") assert.NoError(t, err) assert.NotNil(t, reg) @@ -86,7 +86,7 @@ func TestNewModuleRegistryConfig(t *testing.T) { }, } - reg, err := newModuleRegistry(modulesPath, configs, nil) + reg, err := newModuleRegistry(modulesPath, configs, nil, "5.2.0") assert.NoError(t, err) assert.NotNil(t, reg) @@ -335,7 +335,7 @@ func TestMissingModuleFolder(t *testing.T) { load(t, map[string]interface{}{"module": "nginx"}), } - reg, err := NewModuleRegistry(configs) + reg, err := NewModuleRegistry(configs, "5.2.0") assert.NoError(t, err) assert.NotNil(t, reg) From 498aea5be456343bd248fca66dee873f3ed3b2d5 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Fri, 3 Feb 2017 05:52:15 -0800 Subject: [PATCH 54/78] Remove step to set default index pattern (#3507) --- libbeat/docs/dashboards.asciidoc | 7 +++---- .../docs/images/kibana-created-indexes.png | Bin 178681 -> 59691 bytes .../docs/images/kibana-navigation-vis.png | Bin 106385 -> 92059 bytes .../docs/images/kibana-created-indexes.png | Bin 179769 -> 58659 bytes .../docs/images/kibana-navigation-vis.png | Bin 103452 -> 77206 bytes .../docs/images/kibana-created-indexes.png | Bin 179836 -> 57984 bytes .../docs/images/kibana-navigation-vis.png | Bin 63668 -> 43420 bytes 7 files changed, 3 insertions(+), 4 deletions(-) mode change 100644 => 100755 winlogbeat/docs/images/kibana-created-indexes.png mode change 100644 => 100755 winlogbeat/docs/images/kibana-navigation-vis.png diff --git a/libbeat/docs/dashboards.asciidoc b/libbeat/docs/dashboards.asciidoc index 8bc7ce3023e..9e35cbb40e0 100644 --- a/libbeat/docs/dashboards.asciidoc +++ b/libbeat/docs/dashboards.asciidoc @@ -96,11 +96,10 @@ PS > scripts\import_dashboards.exe -es https://xyz.found.io -user user -pass pas After importing the dashboards, launch the Kibana web interface by pointing your browser to port 5601. For example, http://127.0.0.1:5601[http://127.0.0.1:5601]. -If Kibana shows a `No default index pattern` warning, you must select or create -an index pattern to continue. To resolve the issue, select the -predefined +{beatname_lc}-*+ index pattern and set it as the default. +On the *Discover* page, make sure that the predefined +{beatname_lc}-*+ index +pattern is selected to see {beatname_uc} data. -image:./images/kibana-created-indexes.png[Kibana configured indexes] +image:./images/kibana-created-indexes.png[Discover tab with index selected] To open the loaded dashboards, go to the *Dashboard* page and click *Open*. Select the dashboard that you want to open. diff --git a/metricbeat/docs/images/kibana-created-indexes.png b/metricbeat/docs/images/kibana-created-indexes.png index a42163000668ba81eeddfd8f784c6d84fc73b62f..7035e59011ad020330e8663fff7fa305bce8fe50 100644 GIT binary patch literal 59691 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 z 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

H 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

0|9o@MXG3W?tO-ps{=Ib+#L5oY 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

w%1+13@^`1eQ&Ot^(Y#t{Gh)S(vCF>#MwaY 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 literal 103452 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⪙3|(pi|&8F62$FcH1B)sEW}O_KSs^c_H3?F?-9D)Zpv>y!s556sWO!yi$;E6-7ZT!m zMRQ4WnN+kE?dZM*&(z>yJ*|N?g0OjR02QaBB_s~0FdkLCfW*R;+_65g#52nBbU1@o z=9k~~Kk4;_#$8rh8%Q0Qc{(yP>PO-FYz&SLhSoTcl&$CC z+83uY0}uWJrGAFLIXS=+F@ zQii*W)6My0{nmBWqxLSh@9{on*0y95xQV^mJ68bu@^T3+O+rbYLk$mai(RWSbk@%V z{OaC2%x@mwEJyc!(lnf!3t;c|Lg~?{gO%xg9?q+o{=1+>cToGPj36u_CWG>+_E(-o zE`JwK!mp`j`AO~Q{>MB!`a$6cUGKH_Q&yLt_9vm`Tj%RmPvUuZlN@8w&pvD0L%~lB z!%a8SZz0Kz5%pW*>dozl6<+&00nsHN#NDmnbc1MPX%%kT( zd^n-)4|WK;8k#bitE-|AwtmZ&DaP{-WSYD1{o}Hx%2C2@fYzD45 z#!<*pERBU;NzUijx4Ypt>7vUQkZ$}Yz zARg}ZZAq>uTgAVPJih8%i5Hq+^2s65Pp8>8F7PI+&$Udx;zxY-v97L~e96hj7Dtqp z)hM+Q)TB6y>QF~2NzcCUiXR#WcxLj6#)jj7MwcSk%>p}tvHa|9FkI8du>8ObnuA3k~(4+0LtV1xn~ zMow-f5AFOob06soUn0m4Dx?5cjb`-Y{)s$zC4xL8ap=^&RAeHV)UcK^%F|~S>wU)> z&oEYNnD)58eTIlc1}dFu3^`J=O$S0zR?4RUXNYgqs)?PO_kS$!t4rk|P-vw2oD$ZQ zniTq}%S2ma=iOIo%W43W(EV49{TuK7I6$TZ&2HvT?s~iEl>mOYkmaR|spL(V)Fc8r z-uHXVuWux{TQ}_{e%#7TEzkToL4^0~j7DLnez{O7NlhbN5)BQ%TXHU+11FBc-_*b| zE4KI?ow7iI@sXL46@cGoYbl+8g#j~STm!ksgjzjc*Bt+HI?93N9@Ehg>hDtkIXC~2 zd3T>@T`Qy5;c{i(UHL{A3Tj6G_9U9G?=|_DjEJ%^as&F2F49(nE#-my4w%kSd7Got zQ=pA6X5#(ngr20d2xWIBfv<`y_I;n}+jv6+bjlRm5#<(woqk^JxJ$hGrT z>(i~EK8~MpDJcObDXgmU+>uf~$yGy(i@>(0i-ARo(B#VR`TSmJgkT8vfloOp);sMb}cn0QR8*1+_civFVC^ zUb(e^EGwI)qU12KE`+M8Y8tBzCN~cc3J(>pLzJHNUygdU)Ym`$;%;`^m4a;!2fIUQvI3F`dT;H$_pRjCj0bcFC)Hu|K7IJF?Q1HP z^sj?1@xJ6!LvniFk#BrA!u8!$BeLG8vTqFLE5&qzmh8J7cvFzKZ%wHQmn%R?x#*z< zlwbNmYmHjBpIp0pA?3M+E;uj0LlQ?#9HaUHS=^s-WKbedpyw5$5~kx%d#90f^iEQY zC4QffSvEsKC$l7JASYjAg;claVv`JSNwf2Hp-6B{--m+VTa>9MBX6}$_&`{yRiZH^ z+w&z3HY{|he783~NKjYNL+HXoLl1q`-p8oFp9~4w4^L~#`@^Nkqq(0dF2+!Yaz>)J z)LDUAQ(-ZA){~)qqld^k)3V&M)O5(nNuC$MZmB>GBpI7h^sh z>@NsP1p(wtTUn*Jf&K7Y9;Y_cP4p}Q&R$ER;+(wTi z>QsiU&`s7@8gSuZC@HM%dTL~G8-ry#tBiTMEHg1HFl|b(=VDsO(#5t45@~XgYCPTD z^UF@W5LExJ(W&3~Qs5J5%h5i8lGKIlA^Z!OxVo2oM zzP$~Ujti;)9b~_)F7&E?VF-2c;(sAdW=qe)tO10VQTVc4z}mc2tIcjrR{thuaKLHY zo4gRsC0nVHUTPFUWG&u=BNES>`@jRwa&$u zUa@yrky&T!7>NCvXdLFu32gk$jCQh4A`YN}hf1%if*v&T&W(7E9-G<|RqQUM{R-d{ z6uV+kXginqJ6Xy~H~1@R*vmQD5zfRHTl;uZtrVWyB)`Et$v=7Wx`xV@`i|?a{8Lx4 zv9bG@eQgX18k%o#dYu9<&)=bhtOXAOq#&q}Ta9lrw;*0vHGi5l9|1~&~h=>w^+zH$wqY-p|Ac;q(uLlX&8KxZg z{C@HN&U4x5&0Qju$9pdT4DkJ#=s@)Ks+W|`t(M|za$9+sGqe<#-VTx|NV`8d%ja*m zd0lpGSeUl_{_$>Bk=Xd1!&~F68clX(Z+jE>tGDDyV|;;OuC|D~93Y$LeQ5fwl@mKYx-3!$4jV$CM?7%_B&k0HHCY%Y&@d}mai+TB`!RC}%(%!*^TmQIG*t#<8K! zX*;a${FRuq)ndv+!f#Ff%_BKYz=v{0==#D$VVQ?QUL#>54w`~0O5^C^ow<49Ru4Cg zG&E$OUVxLU@;h6I4#;3B!*I1iuI?>^r^UM$p7j{hX^}o#^2i9FYHUE~;X3drIyDEH60WlUWb3R0*8CP7}S)x!6=6kUzh}Q`S#$4p2c@BgKZPLi3Rup z3pF#Qo_4rsrKYd_O1c79v9#+uGu5(~B$1#>J~1!fL=JVk%DsVwZD66=^Fy4&yEa z$T5+FP)|-23^jM8#m>qaXHH)A^VkWb#*_j{4F6Itj~ZHT0aGR+awmKndlumKy$I0u zs9d$fHx{z1$(g@Tl5u2UGaX=0xH&eqvScLOn>hTNM7@p-ub=D_@txB{O9*8!25BYK zQ^|LYul#+F9Uw;$_st<86nLm2PFh~4Z^g-3>3Mo!{xts&9KYx&f0-X(MY=v5|8E)Q zxk)LHR!YlOs#T(#M9RiAe|Xm3RbaT@dUYJWNc)x%2f+Gn=Lta zH3ZJdNpfAxBw_>pWHmmYSiLPx*)^qp9lb9J_`!VQd8whK0Bd$i7airjXR`uDF32=9 z_#6$jsZ>KQ4Rx{1`i}QXAT`H+$6-7PwSQ1i_G}Z2MH#iSHiZ>3fP7}h6@%^Nf&D6i z=8WvGt=QAvzXLHR1VCH`mHCCNUnNaIZ?!Y4q>OfLhLvQVz=fP<$AB@lVRyqAehcBE zn(~W@wz$`)?_Ow*z9DZw6T?-&3+{Cmyk2A1Kl}j+ziQpJUgZ{u@a+rE5_QszS*807GNQ2zP zLjkTq>#(sg@ZXdvy1o!>a6IvfKXfs}4zqtc^(xOFOhOJ8-26Ha%O<-SW|4`;B$2lX zr(qqoMHTc)z6V8Gco}1Tt<4Z3GLt*1rrIi*F|}AHWV#0KUdyPZ(ljsHVlpj^TjOtg zBAX4n6yGA)G8&|#2Vbg9c~DGE6<$XDmvrRA*(a@7b9QqK3yJs&SwQ%G0L)BF(p}#9 zj4Kio(h&XH;qchhn6fG3CHV&-?&a94s*lWjCMP|mgv=A4E)@pI!b->`*ZO5C>7^E{ zagUTYRFt5#_6LgN=SQJV=g8$`)2?<8|CHL4ZhSU5?3Z~7c`icLIxcAwPqhWgEx2BpYuxG`ksNf^2-wd^Yv<404Dw6EuwXAnPx! zh#tetgh{Keh$*FL+$S?sO;y`VM}ZFHT{1RTD~UM$mOr zD6O4gKeV*0Ah%XQnA#Uf5Qte~iTk0{LtL;%Ic3!4xL~6(K0jK?X_)1e74y}_eLM0& z?q{)mTDyl+F`8%$6cpG~2RPMEVN9t}u>`2WZULsyWg;BeFRD&dF`KGWvxZ0O7#Nx= zX$g-c!%R*SOPUxBw3#Z(PB0Rsju@eTk5`{@FQ24*$G_{`7vr-QmH4CiC*@T5oJ2&L6M2L{Pa2p3>HilpTQvET;K1hmEx1 zsT_y)81dc|em)xUE*& z*HP$^>$(q?L^$KpljS^j}5tk1W1Px~y>>B;^v6cF>_u~LWMo#3jh zu4Ou8@mPx!lg0jh;|8ns_nFJ=9&fls-!dDI14|M_dnd*rLRdW`ZeD((qTZ}K?r#{t ztX6U5Mxh8r3;x^`*=xt0uIKQoHJU=1jpO!@kE7uZi?`xoqE&VX3kBCkUn4SY&R4LB z3yk}hIR0d$aw4sH8NlfpgI&_r9|kb2mz<&0fq*mKzW*L-^P7|YaFw)q-Gj?|yI{5w z9_U+~gpU5oKKyrV+Ki|T78O!TMBdojd|`ao+@LWWIDY+T*UJ_Qm_}gC1E#fD3s%c8 z%&$)fyMf5yZ2QlbGZ8W~AZ9V27<}J%qILNHKMNqqe7B6#t@&u(gRpkaYV>YcjmnI)!@gKQQc0_Yl{xBz@D1(11ZP1Myl};0*spu$KL`D`90f+M#P7 zJ|YGVtnU~tgN>$f^Sb*D-=^}L}`JtJW zNLYGdWF!~cE~RoJj&6`2;Lq4$^&26hi`2a!xKVQ}^CMZIvtC74r+?wH;% zc%LdSI}&j5M#--t88UskV&dD6Lb({jpY^ZQ18OR-q_UxN$s2UtXy51htP47;=h>=Q z?q9o0nk!bpnTV^HRD&~|zTPy4KMnu-cqX9a1-V|nFuEMVI9*%P!;wu-N7mnMxw-v< z>rBW>j>3G(Fj|YQ%YREilOJ8qX#6v+KBP^ejQZO zxb1MW08pjb3i2{gKHN8m*vkpS;Q7f6tkT%GLxTmsX2R_0QWisX$;P)m6WojRaIz*P zE+O&+YC~`FZhS05pIAupIYLoo{BCQi_8#Hesi>`$o1`H@O;*Tcmv#omA7hkS`GvMaN5T*_&UjT!U=Rk@N=leHEm1Z;S z>Wvs;9a8|B39IiX@L_Q709a-${`e43?X(ZBEbjE(=akspLr%55`4SjVCkxT3giQRL z)vM{SWLBp)UP#J{`BDSaaSuCAJ=)8DX0w((AEUr{iRbnRI+HJCAmCO)HfBt7WzW!# zQEI%JbwmmdUVrqsT;Fo+1+l;@kr%5z-G_?1HKNRZTAt4f0s3qZiW!$waL#Vj(guv0R_(ovg_eJo1jmJ}vA{l!!Ixs!;6B zbqo-FhX#yE$?3q`1v{eNAH7ULz$6Fp?kO~fB)a8Tm3?#{5oE|a2_9e9wXmxf0WVR*7{ z51!~BtFBB>{rI~4 zW&PM37|X@974L_D`O|^_X3qzIxSKpHUvgjtj*iHTjuCX*Kaa}YPD$O&#bT$MHnC}KZzjd2 zVIwS6{d$?!))*JciL7<>pTHWC;zW{YtPcGxUzC?h4OBDZ3kyOc0#{CuNlN#}0*1h9 zz6zB<3MJsXr`Gi`;$X`LcB6@`zdLwK`aQ{l?V0WO>WQcw|f}crYnLHn5ZOWgPp|I-4{3bWve!1KZIsNNwa`F7R z599uP#Acw?Nm3%430&F4mG*TB)rOc*!Y||F35Zb=qSrskWO^Xz&OplhhV0q&_-n0q zVWAp0J<#)@C9syIvb{#UGL{<;E#kV6dAn9m5Y#K=s9SI`yWWuIJU&s{5mZ*Z*?Hzh*! z!fn0V4ip1l5cC z3OhUP1vc}vX>h)D$N$N~{k1jOhClgrgWZdmzH;+KFoxJKmThx{9y%S!W5f zz{036F;cufSZn&H3qrn4r8Y*DCUbF149J+9J)m~gE?)vH7NA5=Tx&9u?*|*sd~q%o zdR@=e`qxAnB^}IoK!f0T1%jL93k3MY!*Jp+h530#+^Swi z3=Y}NU==>bP47MBj7XZ{Yw$QaYaLjXgc0UH)3D zA6jDLxbFda&dWq*IO|)i$0-TVC$__?UO&lda^S2xP+tVZ;Z zb-L5UNFW8A{y*E#z>^$rg#2~cG~N%3CWtwM?enWKT74_Wt+aF$qs0?D!|#P!g-#i~ zsWb+dSPeIbQ<=eiD1xatM@h)RW4oUK5R5sQvF+^Yy;7&KQ5a6KsiQPLbhT>OLn(kwsP+~X6tldb96m-|)Z3Q_#_o*fOJZqwwI4yBvtbI8`jtP8=C!jrazjSN zWctele9WV`;nIPNvUPY6`h-}$$l#c*<`HAC-WPREaAki4M_v2g1noT?1BdOcu5T5!>MEx9|IA^X@S(F`5}pd$NoQs4)I3WnWn`?>5P ztpj+wJMuXNrjj;S4Bw;dh&DO=``*c!D=)-WQx^P!=QX48{M3W2B1+I%47fK~T}REL z(DpvM$(G$Kw`<5ipUlB<07jp3X-3hwX7V@v_tUsNiM_MNlu6ueCRkbx;axkbT(Y~W zZx^4$jqh(2jw7K1lRtzO>GJ~UygeW*8O%Il1xjvc0#d?3syW!WwEkTU zrq(@MMjNt|l^E}pjv)u81Fv(~(vcMXt44COPwYt&W1Q^)kX^I?hV5a;6ZOb=h`(n& z&V?5F;`i%X>E;z!CA5x{6qCV|YfGsrFMcWkpBfk5abk+}ZzLjZt_gcb1zB?;69tNp zBAW0NTX&L9kJ@rKr*cYQ4^Mn1=gTyQi-^8-Utp`XGIDw4_Bz4%l++L@GTCS5(^7R( znZ4=8DZea$^io@31pRMW-$x$sg9D*lvh;a#Xf-wb->Cl@m*nqXva==pac>;TD!f>YJUb zC?Xqw`RzTFu*=3Z;&E9}H;%1tXM{SV1-7MUvM?)w))L;7^Q|heg;Y?QyFBxS4HyaIzs6N+M`?pPdva?f#hWl&SUwI!FY@XT$g?;t*FT!EF z9J#C7LMMFOsYlS;5;d!VsLt}lft&O9xZl?R-DHfl1Uk4q69Gv!)P|uWHY-HLNwmDd z#a0(+HB@%^DIWOIT9{rua4bo-oAc2JS|j1bo;SxbTv651n=~{>DcVW_k9I<$f24M$ zKz4NAm~I-tu2Aga4>UGA#OAY(Xw(^wvg18m{;#yM4Xf2DtKRVq%v>rIb1wCXYJdu_ zM$IwRT?O*8Xh=d!%t?~|c|zbVmckt~<%d36`}vRkM` zL1;SMoq+9)gf=dYg#R&8Ohrx)H`K^PmcU~Y(T8@de{&~+jps6+(O581HgZf!ioV*u zD_<10GqC)q9F5KeO1w;P{Y{aa>yAY?B{>vIw~{aM0-^pq0%hzDptDv31S&TgE2K7W z>>Z_8FL~v+(+Wi(qO1PU@5ub(wx8T%&p_t?cJ#d9elLQh1bfP7O#bZCi;64rVR$HB zEfZA$x_>szG}Sfz9Q1 zHBxw$J1n=kN;FsvqUzTKygW9BBMndCIL3czu>Q`B5l}ZKy@a)hAoLEW{4gf2*yw0( zn@`v%2`eLg=5MzlWoKx(OR8wOQPLGA*Sb5Bc|#+j#5puTlx=@A0I}8W6v8hG#&|ZB zywTEAT~y=vN>DKms9W(CM@rb?b{+H$#5r!mcmkQ`MgRx~JEGgbY=hN2csd`K1Wb{0 zI9Yio)yrI8H#`ZoGfY%%S#g4;+Bndb+S?bAxj5e{%DQ6v4kDD}3G`qJy>R`JX0Ub{ zXP*d$5u~L|WTgTGLIaU@wcP^aumbS9|7BvnH^|W@XQz%8l2 z4dVV`5$h4!gA;Unn3nKr`atV!@s?GQOVJFQ<9TKieY2}PTz+5~TJOXw>@3P!p;cn= zH8Fk4c&X9Fq%Sez4YyrhUgYUygM+Sr7 z(0>$}Im7nCt-H7L&3p<$yS1n(2rNX=FOLu=0Srkn8oppW@}0($MI zLBNWdRY5Gy=t!xD(Xn|W@GO-4crdL3OgjAT@T13Anl=oZ2)UjQ;A|}S$~)oOPYf7Y+N{)G@`xB)Rw7yjGn$~G+DROr>3vSrUdfF2ssmO~FX1M{`G z?=YCrmeJhir$`gVk>>Tvu*QNJ7xct^QB#lfxNF7*Ea_=x;PhhskUC@5-^o+jD8||U z-oHNgYL@!)gvrZ%S#<0P9?^jRA6xGjooUpp>&EHWwrxA<*h$B>Z95&?wr$(?8{4*d z`rCW0wa*^s{CmdpvqsITF=t)(T_~i+4123Z6Z{Bz$1j%QF>J5Ced$Z77o#k_BfGz4 zushl#s4(rL+>JDP(Y@sv2Y1DKBsM--e7mqipk#fZRv&$W**czS8nV zRT9e{ko`}~77zy!1A3Z`FJwad&%alOcz^#gPzcyOCvtIIF2`Vh{i=1^uj~qjij9FG z7B-ZRut($BRBMB44f{qj@lK5yxo2bhxqa+|wYd^aDNiKpE9>}NDYoqbkRh=UBBltt zgHM6bXAGx?@$Eg+`4sB<33y@-aB|}>R74k-`^N%4hK)aBHP&&FEQ#&Fh14~vI9Fry zBbYnq)8;SJgVX4e&bCE`nq4tREF>(8D;tq=NNu6}A*8*Oi5sL9a}>k7sv*a76$)$l zQ~6S4SAcj6%`vzJuzH;gEg5ozNkh>M>*Wbnf2hx~`>4)m{}|V)T^p+iuW>5-=i8vn zL{NZPVtaW*NM|I=0Sf9Nytp`djg16qIvaVH*k57t+{}zHt=3cXAd`wcNeUsbbYY8) z>7gn`3l^9la&mfP_~sym(G_7ES}2Y}(+Bvb@pJ!7jsgE`We@TUs?2xoKwl5<64ts+ zLl!1_PG}ESnmX%#?P5*o3|iaKl0H_pv?;6!E()|zSNy&+8V*1S58XF#gt?u9^-~^i1{jKxEj*SC6f&E&TwXZ!C`d_$CRaZo@EI@NZCFsEFRuzrU_ulr zj|y*(DP3ZMFX+ulPw`48D>YEP!TXOnGRUcRLeaCnwIgoUIlHh80mcM*<}sY}FH3AN ziq7d?F5O^?6pCXkLr0{gQj~rrZf2Cu2w0HyG270#C^Nqb+{Fwcn@;G$HC5nL?9A9V zlt%%-N1&K2&d4U*Ku!%Ru)`vkCc^Vl)$M>^kFKV=P5DwOWjNW@YjN{5EiYBSo+J5Y zwcLI~87>J=1>yW6)87)jlvYN9UL6U589@v&@=@?tH&8@3VqrZ;WP^u2(;WI$=H0?*CzQ@e|q6|cUlgL$y_9Wf3^m? zJ;PohfSsZ+@sDu*(@USeM_WoIgkna6vzmZKo73%7{?8gfe_~`C;!b%*l|{7O*E$Wm z*GYTb6EIM>4R?3dDL>yNYzB)phrs9M96$p2g88){K_^vTN{ycX#Lf+;gaiZ-Qv-GR z+~Sm%$jM^KaP|*CBit z!-#!kJk7sZ7N47-ln-?x$AN&KgT#8Al_EMS$7GyAuEQ0kcD#M0Zo0*66gL1`B9dW8 z#cDGmG6AIg?QV{!HP24}jmg)*!~glprp?;{69n-jzJsr`H_pzKcBhOzd`aT!3T%9! zq>JlCuC12Ew`q=%c7axGW^)D2kx}*wu2c4-jDIWwN{msK=asGQm{+rWu%Xs5y78$$ z5@tJ)V?$;w`0(@Ogp&HU0@B>XOdyW!cIL0qs@ExJf<=;nj}hYp;i``?u+gW}?Jvej@ZqVxCN6#7w+)TM1>=fjjYa(m15q}+cp-Ab zTvG<#^!qlBmy^;pM!+)$vH#-9T}=aHDWoMQi#vT(=dY_-QXNitWjTI z59X>J(D}-Zhj?ZM`8? z3-$wbjf=Tt$QH93Q^IU)GLyM-RfTvpAU2&WcS;&~Bo{EdEw0kc(n$CatS<9L_RI@y z``-}f9jEp@+;AG&L82^TvsnPO^z{c$@yybfv5XpVCq%uhL*CSJVUu+-e(bC;C|U1S zD|Lb%1iWIg$)+;%^;-HsmK_81N9&OOCtfNmsSd}~WwU&ELTtrozhR@RF+o{>f}O44 zaqg>8rC%rB0yP@`hLL`r?=cC(i&y9MGrj4?7V9Gn|9b%aAEu}S?E4Rp$};#DOh;A} zR?vTuN4fOCz!;>?H`{nu8?9=Y&%IjNRljJhUoDARbf^1yZWsdKx&bga50$OC1AwfO zSN(CNCL;2Rtf-eGtyNDxj7fg&h(?^AzR8=fuzV8nIdaU_@T!VkM$wX()5Tg2-gYV# z94On7xZL1UG1=e~5|q1{shrKI@tJH(8{?Esu1C6@oL-38DV?wpK>Pfjm=La*ce6dV zuiTxHuHC`P4gbi#j@#Wx!pokJX?WcYvMt_7QRzuOmkQ5CyiYz547%XuB-_pXFYFa( zB^<%=QRV#|{;2X zyc~}&NZyb0->jsamHwF|hh+Nib>X*HJWw!V%7c0FEQ*CH0?ve)qJs3c|0 z0)A@2pkpnE5Asy<55Q|j177U5CX_ECBt4w z{P(R}N>rx0R_c10+_HR3U7So8)76<+*;X6XMj6QNZ^ba~dPe)Ez7weT2?zy(qD$mQ zBPl-}Fv|%uNqK6o@VOo14%4oaiD)M#9Boqi*;E4>(bc;*E%mN<#`UMmXmq;Fer^`9 z(3o6P89ITrHtvLR5^`iP+;~F5-gi>Uqm?2G&*qJQ)|~X1Tpm_o=UbDu*3(ENpP<#$ zW_N1Gae-m5m+UIOF@GaV%>Yz$`qsa2xJws`P1~K2jivHJE2?n6_Orw%dk*GG(-}V8 zj~-zcYhHB#{!s=zD}oiN(maT9#ynH*9Z)t_?BvH9EP3wqu-`FRK3;8NSO3v73;;l(!Jt05CMMnCVraKGw=r&@uavzbG8J4^W{NhkG z(`T%@2*JZ(xv_dK$+~3%R+x@rke>unojR;5eL3x9Tj*hCwBF=`-MiTx-*dgLOYJ^hrR<%I? zDdCacF!Je6eI8i|>rX=@n=sq{1{3MI;_%#v2d$qpsC$ZMY*<|j^+I}Caqaa^q{icJgqu9q)3tiE z6+I_yKB6O)ZsDV1mJ|0~gS?CsDSGJ}3R?9V+RuDn3^c8NK*)2Ig^^|_=|QpgPTa-5 zGIiOBL{<3)2+vr=cF%*CwQg^W)tO_|VL*0W6AgsqXbd~R>z&8oV2g6I#x&3g3f#R$ zmTede@S>)~T#b;`5#>=zGCws7gIDR%AAdtRItIoD`v>8R1;oK|e&szE2fXBF&2Ca6^LG7wFNX~rxs*^s8CuGh4&Gz zo%;TzJ60;QRqnx3L^S=w_;tkUq(Aaj(p;NqSj4|lA7Hp%!|8_LvQ;J|c=Bi9ll}iZ zT=vR=_+j}^xJZ^Sj!7qU%^837Pao>3sTfWC7oe0&0erVfbN=DSf#zGDA7hQbmX?k@ zWbfP*#HK-JYWx&trUU#rv}lKvj--kl%kVIujK))7Ya0`RwcyalSi$a z3f9w?=?H3KCOIN{3R)qvp)?#Vm}G7LYsTGP*w`>5q%K&-*?PZLxmNZb#r{E+bo1P$ zgoQ%un2tIh>~Ku1xmdHKEFPny-CoHk;@qV}pIpTgPMC;eHbl3**jD2)kJbxI3lfs^ zPQD9Nt8Hl@EI{XWuixl3o*dfQcY3~A)Mt4%@cf-SFkG^UPXZ{~)wLT- zqY@j}0s|0`?)kwnGLY863{J85BMnN*NwUKe2WT^K*KQO+2 zr94=;`JnQJ7(2$DfzBAjP%@KI&8{7CmtYb3@QXu?ME4z)0l}uw_N#*ndB>K!T98<} zArz*}*E`$sYOyWk9-fYRa-tp8NZJZ5^OGx+O38_a`3li`Rq!iXE$%l?()7a>G%NM8`Qf9dHhPRxUbW`z z7{{d>zCDFNI zHXFV|7aq!r3&~rX?tBW*}F%nFY3VeMZ{uxB=SL-wWF*%5HX(Ew&p* zK(^W2(GH1vg-W>_Xb~GkSkndY%9#+I*ub!g209gOR<$Q^ChjAM#8Utf z=Qr%eoDhqbVUp4@9w3|hMduKJ4jro|NG2r!dC{BdTJKQ}I6`}s`AC1Otz1)1PxP=}T!e21I!#qLdyWt}7KQd*^7I}O!A2Bj4Wj+i~9B1bYX{-hi&v=}n zBZaI0=RuhXeWt<6`|LR66A={V#tXKaV8y~wh0cCpmy6{9IH6YD28c@NKVn}73G5|n z)JMmB7RA4_I2}Ofry=($<@G|@I^Aps_^PyevPjha=<`|dbm>0CoZBrFNxM5Ph*WdRfE_!kus*B$)T{Fd4M%kC6Nc!p+2;krY+_xKg9Sf?Ho>uGdg_I z;&M7ef#Tfy6Yii^cfbx7f~`)3YIk|v0^M(}??*BS{LPGUbK0-7x(Y3ZG}QF2zC7pY z&4@RtA~E+l=;vFm>@+p>zaoRA$m&sMj1PZA081z8wqoylJi(r8xiiM6lpR#lOBCH` zPiA(=^TT75sfe2b_&#;jIqc}rawkAJQHVR2$(0CPkyjGpMfzM~+IXlwJoc-_HXpw> z3Zl!7dOL|tXd_2^m^pe6CAMFmO*{n{F;G*ZNp@Wmuah&2GnHo=M^3OTP)wf!JwU76 zTexE{^YT_vo3qtw`;|v>+ArRH9n?br=7VCsw6pP$`hv89dEiPd(jkA76xv6WVC7tG zV9PH#l)|~-U*B$>pDd={Ubd@ihffOfa62ZoE8{A6#3SkU`I0=y(l36P1>y!CI>OhR zCJ$AG7d`wbh4^*vs>q!7Th>3D70;fD8tUu({hLJ+=K%X3SNgx?1D`O+yZ7HGU>Y$? zl5UcY%;UYQBLtyh%|kQ!)2HRCrOL2}EwsuZp&n$hm;(RdQ~xu_qJ$coyxVz*%>-UA z?ezPr+b^#tCL#h0*d6-^8xda8rq@0lp+Cny2g0*ju7Jy0&3|u3&KmsO!MNY<(#y@I z9KCF~5k=y_G>0c~qcE~8av2bIW8`i$!~dw)gXixdaXNi6!sm2A<%(s`m6r?rTPsNo zpW7~%2t@RnAm=K#$so8R{!td)4TLm+y_hNt^rzHP=ya!tNtIU5->wC>h@}*=&G{Hp z)vrB(y0qO=sWz6OB2y|_fCk>{8CUezgZxxpg}}=yo+o^W#->Tt`!$904GWO2h>bBQ zX;dt6MYdm;Ig^}TZ|K-Z>fOx#OT6J;q`2lvaJmi~?9B@NMz3aI?6Pg)LxsL}=PON* zPB$_U)R~ly+>TH=Qy9F?M_(-`YHjxuuToTUCwqAS8$oYSb*OK#ls#rQyWME_s`9W@d`of*JM6jUa3mR$NDJCz#ju&z zKOx^;ypO%*#;E$^T5Frn3$V?)ZXd!Uwx6PMJQ_^OnO=atO!GUC@4*iS0|+eDvo{cTa~xfd?TLQ8SorG@BQ3T42NxbBe5CNj zQTT(^Vf_garBc*8EC?SxWT26N%lx^IR)_4qy{*bp#$4FLU}J0^%4uQD$|IDy6&=AK zVvxZ37pPzN5AatcImW%Zy&g0JXIvUnMiTPON!1ljXlIfczZ0isrA9zKGiOSv0ya2o z_fNk;SGx`<)-k74^Rf*14m+u(|2~om7@TTwyX&8Z>Ard_LaM(5JdcL1Mv@<1eYY&N z!=;Gk2nAE4oT~BGn{R*?iyGDM=Q&s-nzwp=XnJ>kkR}`y-hQo2)}Ltd zcDP)QKQ{=@@(;#2KLF*JY3a3lK)TuLjXvM>j`ru?#Fb}9H0xClj6bL$q2c{aOEYg^ zKJ%aMR}J7lG8Rq`!-WlMG8*Quv_l9#Ls1Vcfib6f?Ib|LN)3AuxEb=ISy5tBTb(mi zE-e+VXWKd0ci}F~fA@5D#uf5p;jTv+ijd*YaN+d0#L(_>A&5^BB~wobmBb2-yFRAGWbyE z3c9#e7@dgWj(p_$lqSEd!h1K_vNS|mV2fgA=sw-4pLnX@&Hu6Hafc6QC26>xe5t!q z27`b*1-7iOY~i@f&FErXLqqDScYP|%o_$^wqx&0clGFJjIPG+?Tr;vI5~vHYn^;!e zxscL~rIM(&_@+*2BeKKmgb5>{Ud=^Z2a!A z^X_1}8dB^)X;Y*1qeQ6DesTyIs!o5!lFR+f5`)iI z@eNP#hGkF>wy>lg_aujNQ3BSMFmSWG0D%0}?u90eLEi^>wVt`iQsQK9^?pN($?gvP zRiPz5x`4tX#N=gF4^E$!1^#;`x9<$*k2p89;orwEnLjrRKS-+!=_?yqRyEr&{a-j3 z^*#in!iKCSjVbsycGPP_4StbWkkTL@zHno|;q8Y8R&)nq27C8~ZuzqKI}ogT)?~dh zOYDm^va6C1wYwbQkosHiV#)0GPjK#>-IbZ`MO$z$-U*ND9q)g~^6S`JKZw8Xf9O<6 zgUR7@1+%DKqY#=BB(w|dV3HwB+&Hdq%4?>f>(5?CulnwC4sia64T}&_vEe*7;nBB< z(m>F`aY@tshSMWpZRQyEooM0d-i!n#AOAB_VF|@gUqXs(F|zMy41ymVVsQ6e**5Nm z`t=qO6}ePp-Ssl5vRR<>xPi-Imkm!;b{5>i51&M2vxUscM}QuXY<5EwU*hSS)gI!4 zurNnchkd!a%nXV>^YG4$ouU&t7(c^pVq!uV5n72=H8@@6eR^<%V7et-e z7<*mPZR5lP>0G%d_uODEGbR*`rMdhPM6hzX{H5ZAUV9i=Q(zS%O3_VzjMX1sEZ)(?fH_+9y* z>WtTD%C6*67&t0Rs!D|=`Hr{-j>|&F(#6ed%^V^QdwU$BudO@ks6s83V+e8_(tWj zS1CNPxjpu4+29*2l+?Aj7-OB5r^9J>TgZce&g!3uTQTmvszOiUUU4@I1qDi1$bdWZ zI$fT6oN7k@4Z;KdCy`hJ2_Oxu0v+X(7dHlfeO_#>Hz{UoXcNNyn)>5hb~4MoTF#xs zn|E#^T+$pvC|=)=5;E%v472lvxJVJDh;y%+?UnBa|Iu3apw14%9G`tzCC!oPI2}c+ z?o~nrWOG23t-7;?a}tkiDC+fBqs{Mzo}K+VNM1lggeM1tK1ns##NhoI z;q#m~ow-8``dyHPXaU zDA#tY?&ucI`C=PJj(gomrAi`YegT3Gd#QiKkG4-rj$gw#Iy&nMS`6M=&YlJ`np+^E zIFk8x-pq>eV92$l*Z(hj+M6Lspo?geS|^-djgDwYBiX@8zGA5&CPx)ZxFuN<7a4X7 zQhL{OQYS>P662@dCBa4~b0F}5fst9Lwk`&Fi+m7%8rwUns`V>sscQ{!zfS0~4R_(~ zy)2+sa&5R@)~e3*DAjlp8MX+e9-qWaUvLAcr#8cA&gj6w;Vv|d%L4YZGkT!3c81g| z{vpJYp<(w`Mu|J`z9DM(Qu2Un_;6rhjlhmhGY>c9sNSHo_nsY^E@eM*n7P!!JC5o^ zqZ6(J&QsgVL)8L`i2>n>2X54gVQB9FEq8Bf8&Fk<#_e(*{29r?#Syn9oqQ|lwEY`e z7M50{k7@ShtcqA#CV#Lj-ba(Fr8gn5TURa#4#w(tBE>=|?dtJAE;VGaA-3N#MTz)Czz}6)nBmu~02gv@mF{1MQ4YsVt$fK!cJnBl2p4gy z8P0Y)P}M4(@q2Td)%|!$){nVB*h#R}h3_V`wIbflWzC+^M zLgw1=3&;d>l6nw-{|td9LE^gY9CXhLls59at&7nC!@X6J@#Se{l%Vwa78Y$9vz=(| zrK<^u2tIk@X1b0YtkUlS?~YOA^Z_;q_TMy6P=9IkYG6EGj|}1{8T4W7O`LtH_dIJ^ ziqye3nQ4_C2}a>B1ToWz+~f=2F21!gnLVSvzOoOVPf1xTPA-)6CXa zr5KByL$Dq;j$9sbJta5|mp0CRhR-91IT9vv;y1=k zcbam>++aoIh}j;v)G;%+^$%^$sX0BGd`fsBli;>(`gir^5bpT*gcTlu9-eEoJ z?0ZUcW^2`k4ljVyMfTMg_u@S?$I}MAd4egJwka84O3Tg(c&JZ5UCiURDa)WjR9Z-S zTMm$`cn7!+=hUeeu^jI!#I4zkw*n1rT)Cq6` zZ`_RJYq_ZLt`$t#DyVD1=WyAhJXv7+x%=2X)qGqAXoX3XfIt<$J3qxrg%kPqS-7Y zWbrfeWAr49yL;sDRtx5nsTGs7iTbGOZX9f zU2H*_UeC14EXN~6u-V)gv$Q*5@zp-@GNTmBEnpJqf^x3tl~zKbHW|32)7mv2%G-Zy zD8ZE=;X+4u&FW66KuooNjI*=_9^p#58$bxGS7Tb5cCAaNCVMZC(zfO>)oSN%}ctGIM&5B0&tMYwcpNSKK_ z=FBLcm}Dur-Pb~1>&r_hdz3Lk#JZQvzwS>LdUc);VoPo$f;8tB<3wqU*3=N)-N=K1 zS%}i1=IFRq_|CATGn1_P>K6y;7LpPYQc9NVuFBIbW*x+pATvVaxB`xpM*Yu@Zf*zW z=*?^Q2eR;V#yLo%ZqN*9|4Jg6P*jQFqm9M_KCbvbmMFMv+lg06wE)D{#0Y^yP5j^T zodj_6!HxwBtfSXSu{A7X-o|PZ+nJMmL1;j! ziMEC#or9Z`3D#POah<6XJQK+neT&Yp1Ae4xO?QO)wKX!e_Sm})uN`-ch=;+`sNEbq z2TmF}60?z=;iu8T+}rre?ZV+TuARp_#IF5>fzE?erU+P`piqsLsxgsiWqtV*IFlW* ziQN7tm48ELDddi9W7vE~Mg5<&+|a{A<7V_R&%F_T+MXrwwgj`r(`0@!zQijtNHhGF z!vPC{SLH)=&H1q^3p=PZs)d{)q`XnVih3&6<7LB-RM(Bq)YLL2JS#3(R?rwU z^q`dZI6;?5JU>N+JaMw=nM>8cN5PVO=Z?V$nxVskDyNdXG;)?7X4TQ&P$G3kw0c|%VE^dMRTPj(nCaNOovep`sSsZ4;h{-mC-PxH|*a5z$AfH~8t*R2Ap25T?$vD=*e}w!0`++qP`Lz|R48-51{5mjo zeOXx}*5WWI{vQ`1onxR;q4d{pP(NqaVSgobg$G>!Jm=y)*YCYFNfw=F4R(y!4D;}> z1iEp(3CYOOPi7G9_sR-@)-?5JF2yqO!|?t;FrLCkv^Lql0l0YKG&zy|d~G>ZoRKkVZ3vKTo&X zn5MnGVeA+tC@LvJNE{XgPqu8Dksxv)8GUK zxzaY5RFw89wT~0S5Q>N|s-)-@+(Jys)CEm(3vlnog$_*xmUv3AQ*#8E+>N)oNG>*DNUf+n`nOVqg6e zrY*9Upf8Z_d~_j*PANqC%uSXG^6X;;^4yYCx}=1efez+VBtzUfVe^}u2xmNiQr*Cm zHvzfPKv7FoG|+DI_GG@~q}}NR%2IQIhQmaju|KDZ)`Z=^{xD7`ut&BiN)_w`tY|_y zT-4GPxGMsxD_*C{KS;SELCK_hZV7INSCDcA-CI3*mXOb3JI0hL;J>{9j`~~|D{E6* zK!M@JSE$$XwLOC8CtdALdFrN%qL@dD@kp~LgxGc#MX}{bq zVfGWl+%3`H*rEk-#yHvMfl~2F5jO1a#ZBq0yU*v;v6uBGiQ*1nJ{SJR5+-{}{Q$A_ zh%X;IuV{$$eiQ8g04@K$G&cdz1DB0Y3+g4VHtBMF5izUNf%*~5Z> z8cRxjYHJ}?_CXiRFzN)|{n zP&dZ0PdVD@?`MiHIjsV~_tVCdD3AKeEp;LY$X*g9e32AyErDeL;%N#brbtYKoTvNStrAVCDmyTc$LfH`P-%vaqz9L- zhOW!f_a-0aI zm+nbt7!Lv@sf6bKl;n94LW8Q-UEa9X(d?l|?oTD5Q!C#JwnvR4mI~E)#!&#yWQL$@ zqO;ha>)o_2T;-zT(gSPj5W~y$MtZVwX1|(9Fpnpf`BYIK<^(JRR9vb1=6G)DuSbN` z$f>tT!b-A@3S~L8Yqck(=a#1}yj#T|)g*BwyauQ;+LE-22%9d`{}DQyN$>7JD9A$b zhs*_t(Wb-Yv^aW})A6dHEg|wIf20~Mw#Oe$Rw{6QFZE#{FbBLV{4I(J;*#5m9Q43; zO-0dx8nC>5a255l)7p%X8=5!IiTP;_EF-J*L^3Jdf%z(zHISM5kKXEgmNbt&9%47& zdWt$ER0m~wg8=Sb)?@hfJz9VG;@oY#q-4Tt(_VQKRTVE*Ej`AXpLV?EBiXWOQa~^C zHcf;Tiy2P1lT|sU_;J<}VCLNnd<>lF>sA%3fBRO)jU__$RRL(U^iUz&S%8G=`NgU3 z0=>z3pJPy#RFWWcR7%57VIjef0F%66GE}ipcAio67MgP+CZDDY$Z#F5obD(u;Prk` zcM&M7fcLbQB%?Gbr8Xi?YzaGCB8>=q zL2Ck>)$&?|995RJ?hy$GC;D-W$RG1%x7?WSH&tId)QK*A8UH*?-#u0`5c!wjlj;pB zsJvAqc&Mz9W((YVi+W%kGwhJrpfoHj9N6ytZI47mMh2m|A>`#VqQsky*RZ_xPtr9- zCvyU^L4ZjxQ)x}Ef(3uC16%4+_%88m`7EYB*J~%hTGpHV**o%0%Tj4sm?3*pBYSBm zP@xxI>z>%5KDJQTMaz8qer|jFVl4)I-||i#`c}&H0z!@^=#OfYS*aj}{<-90vOCk# z(`kl48PyQgg)kr+1zC);Ky8WfiGHj$LoKBe&@kH)(1%VKCqlkU5xOH z`*%CXI?@F5i-@FCgsK`&9dkdpBu@;!R_kEV9#VsqvC;w>6CuUGxev6~qXZnT;I%DE z1eabS-Rsy(rH$G&Q``vER5afMCD|Su2keFy?-?R1{k~(pC$=?Qn+8-d5CNF~dt~~b z?0X@-EiJn>duZ}DI1!Apx>Tw;Y_*8`Mi%;`R`K9+>vT+|fhCeEgO`&ppeyzZ&Ldp5 zM1W%s9xOk2tD24t*-=g-zwrIaW{M%=KFtS4%7euKB(svs(p1g+s( z4@fT}f~|^bW7O`z2|A|BR5Xb>-?1!sFXX~J_*z4rYb^Y}bpGjn40f>$d@ZxzEI`S0 z?x|c=BI|U`=Du}xQ=K~ZtJtx>N@m$FyY*`Gp%7VHb8&nS)6ZVFcPn8*WrqBz*-}_9 zW#oV}o6Qo3x1r#$MFvBlzyT+u!J70E{f~iW`>)y#*}WTEHOZ^*9V?iF#UC4F#6+m_ zyZJVUhZg2U1JMyP{cJ(y2cpab!Gf+)?7<&9s9#JJ?9}iG3ovsHE#|DMHMLN{FO~m1 zVT5hM*702tz!hi&G@j6gEsINjV`dj^Nf&8AilN|C1(0rVIv~(|P#XPbaQ(CE|v z3n}z2cHbIvbAs?p)(W{_$ZVTe^J`j4Y_zC+`3d^69|lkDz_-PgBh_ijn=*^MMchi$ zL&I4utLE;dXx##u@jiD}#?;7;<@Y3c<@lKJ4ZGuYx(;WS;z+0AcN=qqltHO)e_T)0BG zA{gi8{TXjf7GGc+jqE5nVeG}ylCTOZmvv6-D-&5QK@Cn6S=UVwk%B8^)U{Oby}d94cE zeguaCR9CtR+uR2|os6WnkTOUjABL9)lU0_4Tf!&oHA4#>`mn)drU~*i!I5HwNcaxkf#&v5e-2EOiG$YNW`gdkCXGVjs3y`!? znU3ToeqvJwF=zifL3aOf(-UJS1hkD=AUq44ufGEAzCbn=Q+KoYabe^~Ld}HHI>ptK zK^VWWlOxC|g6<@D-oX%McMYtq$Ff&BU&aPwZ-dFP1AuwduTbc583SQ(l`%hL-m;%_>O_v+Q z;W>n2jOotI!}++0&}MHsqZ6sxPgkng`K>QxTPSVtkm_XeLq|LWw)*75+tb#W5og(7 zl*NYW&L~ByPL1btR&jW0_6kZ-dd|)f{O}$MvwrU@F`DhrWy!JhPvTvWgqar7nhT6( zybQ$;*Tbsmhn2G_o;~C(eC%o^z$!2)E72RI}%^&*r_pYT*{%Cx)@u8b$I(1SKc~n4BWh*{knD3ee~FU z|HHoY`Ne>Vz_G@LDkH#v6dcdL1u^|I()2<`6#>HN8+h<1xYE|UV8fNycgsgYv+VOs&iEJoI&mio#CRxZ5uo5 zeYV#AxYJ5MKHyy*E1O(xL1uvA1t(C2teEqC?XB%Q@{dth2F7vCkT^dIKA$z`uEj{K zaXUe8H^fX6t9GYfk8+!(N~masrj5JhHZ<G@uaLes2u5Fk^{qragwz*2fGfCei1kjVUl! zmLXWD2G;Y#>>`gKI_KS)Y{tWf>X>a(MUMw>QWi2&K*1jbH4g}!QVo4T1m=tu+8f# zP*o$E30AmH3a5ah>ErHY!xC|Lrxa2;rpIq2;j9(6&#FmpQUSdAW5cB?rS`hAvNZ9M zrW~Ld#U!!9#;WfdjR1*ZX4DZp@#m0KrB;q~>;US|yyn7ONHelbO@$2`J+dLYI3p_? z5(@IPc}k?+6$3``Dgz+GUmMHST8{YMI5R>;XDb*ZW!b>R2#3GsNlL&+0AE)#oGlvP zn-EZnWYH~roXJcJN)HuUIjEY&$P6>i3?>w*ydEby2veIu7&e=c@C zz0RlMM#;F@K`Y0m2;Q`lp^db$-ZB0DLY|T(NQeT#3S!~+F!5eOkix$GT!20Zl(_qF zJ8^3lBs#9D{Qb~`o5f0v@}Ze`tXhNk~{6DJ3A6 z#S9%gne0PxWJY-TvJZO3og0^6P}RZ7gMwiyJzQR;HHuC%&m?hVwpArRA{;HX;Vh%v zS(1T#E_RsN(oec|d$1+r_$WoSuv92^+@yyp>T8>r*E&U0n0Fp==qd;Sx^$=T)M8xi znaVUw6Z+I?D{gio^8X(;VCW{8|0G?2mNa41D0Ai3P9q)%VgcOV7G4RV#QCBx6*H3{M!;yI}U#Vn2EO`20Xxgrsfrp<3 z#SES7;BAfOu9FO4Pgb(qOd!wH(GLmYOIv0=n2$9sk>sFsQbRLJ_vekfmMIKtT!>`I z+o>OWv!x9m-b4EzcZFmGs@R#;i5wJU|>PF*zuC9dy_;6^&?{WzvZ@B>S;lRp>2rP&sYWpRlhUZJ?hFX094<6<2Hk7Q^r~R z<-dj(zGt%n3KAk_4%S_9QHVZ_dHBY~`kOi`ozzdf@7U&&E>UYuZ zEUgFn)jVTfu&vu#7aQ?hWnQ+4Unl0WW_{Nbp;v#H?w<#Rl02Jm@zxnixq|fQ=maDq z*Htw84pZ28ia$gns;xM^o;p{>K?4@V2r_c3+Z&`(z<4P3Ec0XVdW z!gINtu34-@#dxgjq*}B|g`qP!=&xlT8_ZtlzBb+7`*8MAWPJnuHI8 z^Kje0@QWK7MN!+y6TDmM%U=E`9b z=NCg+(BsTtr3W~DH^Qp*^g_Q7XhvDZ;+iQ(St4EING~6>Cs&&%+qcgUu+rZac`fe3 zNGaGX>wbo)s?#q3D21wA&-AoZf^d#G9jdi4&+}1~h&oBP9t+#luJtz2nU1ZJo`-}N z^I)^*NGC15mY16MrWaKI0nM23xEKR;wR9uWY`UDDR7=}?!sGv++W%kStiu48F~ZNK zbJ&=X7sAqdNZW^<;x-s4Nj6`(b2m9I;W>hY({hnndS&e`fD-UC3ReVw`|7?Ecl+uT zt}(VtY5p+&RDr|SVaLi+4iwD|j`e(_Om)s&xdU~kRqZBRlz`%WFacUz@QhXdD;7s+ z^CbCi`*F>{VVp+yy^~kA%}2xf)fP@nAMGHB%$STw0$W6KLzTGaQs~f8MIrg#em?u> z<N8<6lzBJfYCR@wy(NJbAKNWV+)Fz$I2dfR2 z2C0q;orma_i5_**PiN^5P-Z8EXQ9qZGx7=1hCezDkw{f`_c;CKF)JB_4}VSgfJNiR zi31v>SIBHuj;hdc$j63jFMmlB(q19>>E&C})}j1hY9&-0rbSWVB*Unj4R;j?5f~!N zg&y^&`FVhppd0_5C6N37>#_ghd|C<8sU2XM$Y?94L6W1X9zzg2-T!fMad#hKiK~$n z6Iq1g=S`g$Nuu*|M;#?4-?w()6n*{aHm%buT85qL=jQz)%~+ABs%vSB)4P?kC-sUI zB|wj%knpxVre0o28nqOs2C5$mR^3q*;>|22RBC&RH;H|boTfBF@Kw{H*yHC%MBZ(l zs2Vt6*xw_pv|A31jaF`kPSL+|E@V`x+%F}8ow5gxuF8R<=CWXO)^d~y)_T`64wudv zm;7735bnJ#WQh*>{+!uUi-8t!3bp&7+Pld19pp;<3ekjZ9zU zgN6Fg!6qh=Q@Xvz?3Z?ppz#xgyX`Rg1{n(O5nF9lNyf6rCQv}tr+L$(X3=lQEe4Nc z>gLiXC$8Gc8~{iROSG-o3Lb!v*0J-^gp+Y_NNx?tQIqGFuU-|(?B&K~DoRi+8;@X~i6Q=i=S8aMxL^}o&Gw|6>jK+!WTrGq2?pqssc{ggUP z8B)NHNA3FD)cHnB6QO$9Np_P4i0Ze8?p794b{_sPz6vbR5-1GvW?g0tFt9r0V8QHQ z!Oy|qPr(nR!Triq*nC9TKJ$8?%!dS>;r}46AL6{@r@cK6oMB3O?Tgr^FT+p@Kn~Ar zt$wxa|C)-f#{aq;rSzfauRpE zbI6}-`0$58lhL6AXbKmYJ?xS5r-gA8^9DMQsQRJmhmMf9fJrGgFyThL|-#^Wy>@ zYX16NRv~)EPS;Tp>QZGUu&zrOJ%u#?A3vsrzEbnXjDLB6Qew1RVa4l1%|QpRE>=6w zKc!fY6i^LVNEmxx7yc(Y(}B8{Lbod=YBA8b$vZE6fZR+MR5XSWLQlUr+nS`;-}d^G zcFF9}LfnggKV{7nC_4a`N}+kXDKB#ZJ&+I)u;B1jL~agi^ZUV#{qp$$ksrVWe6O@z zOTaT0;Xca@k(+$;NNumR0NQMBtc-X7+6-1{jLd@r?Su5ErL5=!b0y}BUqXzXSQe>K zV>yo6$y+wa7wS-r+v^aLww`Qh-V8Dx_V0$4qaU{LsS~!9clOgAF173y(w>8qYc`XX zuE&YhnK7v!xGN(U`L;Yo0Bz6d>OocggcL4xrmG5Ftr_k;eGPt%(ENikNxObcuA7LSP1)tCV$+pyX|;TM!tL4=2vpbpL94 z0zT)cFM=MDWMi(&0XITzEQipVZ=MS6_skUym;ge^$WPffYV;zIK28;~n7 zBN}LG?=DYS826Nwsh`fiSJe+id`CJ@RlznK7(ZNR!jqPD=z3-I=QTY+*{gl$puf0- zk}DO8Aq*$_IXf(%2qhU)W~a%b!QCCMt>hXVXyas=8H$tGcu8jVYw@n~_a5`!0tHr2 zg3YRz+c<(VP_c8@+Eqx_G$|~&etSaIkCRr1Tm@&sbX+}P>QuhhRe1EGRLpA;vxoRB2UL7m z9$TXXA_E&W!mwI%B(T*?MP_xWE6d*jX*d13zEGP1>*GXVC59z3>D(GLXk`n#cdyKp zv%6EXjmMv)FJCiXIcNbw2$~r&*)dnHn8VJs{3XX~?fx#iVrh!!$}z9V9eUK)pP5q; zG$kgTr9P0j+zlUi$wWFi*p3sS* zoBl{#E?LU6M0l7ni~8Bs7ex6KaASZ-!$CCNlm+8vC~R$mlL_}hSnU9BRa?Ql?SdCu`uWZX)rb;I*@Q$rlm(h_vLPm z5s%QJY_C^Pv7(ln_58Fa&HKV}t|=Oeptiu-^x8MO%-%hfQQK9=Mu>tIInq(})s?HE z0rzA|V5!QAh9)BwaM`KCh5(duq-g!-y5P36dXa7)M)>n6MF?;L#tLjg(^M5E(wQtENl3H&bI_V#!6w1^P( zv&=5AKCO|JyG0#D0ajX=l+$`__%kvwJBx*Z8LxsHe}l&-_JdqC2d4$Hig1B%=Y+1g%n48*f?;- z{+8hHQH8WdW%Z3?<;J^4vPgaiSoSD3!PhSL@s-oC)LA_c!CD-DDSFd#fyV0%TFV6H z5D*ceSk127%d?3i|J~q19|m4XS^C&*valsyo-j3OQV&+J_+FYHv4llLB$)(FbKFHhy#0b2WEb{2zt8AI78_3HN+|YyieF}?ks|H`$Kqy?@qW@lg$~u3 z64g132if_S;dfz|4MQ)3AKAPGY3f}BF?kBT2EQ7&`F$MYZoNPwm7W2bb}*nKU31po zn``B=dwFuw_Yz>{J*)C%90Fgsf9DU`V;g#C84CGLuhGqcZEf1RRYn`c!xc}Xr&p7b zG!yeppV?0-QApQPK5|1-o-3&`28t^dX@Vb>#0^f0m4}H@gr6LrDHK=Or^>Z!-3%D? ziV7Acrt9@!p;(YX9X8~5Yz%@g)zl0W_)gznf0xD729>kx%u{7(&nEkEL?Wca&fiRr&vC! zjoh~*roY%}`q@n12f%7&OFpQse2kB6eNNIM^fPFbnH23Y3GN3=?#(0JV8alvsVYOE zjdPYE1UgW507}J23u_{J=RS=rWK)DRBlDr$!uXS5HT)%(ADd#+XuYe)U1sl>8aF`M zXd7e z!o=xxXR25l_Z0hjvWV;*I4s!AoGDBVlMJa5?zTS5*|PrS7A(Kt!z-(CT|Tz&nCM3H zvsC`U`Ma?RBGO@6*+C8#hM8%a*dz?B(E9KM$h*VksDR{Ox*1_(4ahgc3VSaK%Q$l3 z<2fctKXw0^PSpEBzHnwrvM;CAs=Tbz&MZX+A*_qwmbjk%PMpNd)<~kpYzeTAH#BN=2>0DTnW(O_%ip zbA_nv4U=~Abgm7cofEx@!?zcMnC4Lj@@7`~Kiy8eZNvc%geB@+^tp!PWnLr4Zydpw zfjpPwKEZSxPt2(}T%fr;iKFy%zouIdyCy@K4mT*t(A9m?HQ7p#x30I_BA*P|4kLmY zi_hrUUZ=huNX!w1?^FlcT@dv=z7A$TK{rK%qM0A+wjX34>+&;%RogMu{W8~YH;PrekK01&_p|yR= zbl6K;OK!ii=$ASl4zr=eJnz3bpprZWa-N-q+9zBw+ ztsTPUjGkmXUTiOFHxxWiN;Bj<7$Lo(ft0(F=kHE94CEVnqUeK?53-@LCiUie&q-t! zwZK4&sjWFKgoUn|D1)>rrLz;DM9jFBP+Ig9LvN4aM7Xr7C>eq*xkcnRqBjDNbW)&MNDO7QzQfi3iB{w#^I}&}WtA!e!8^1Xxdn8LrZF*}51X`G=s##|D3` zf>miK!fum6M=b^+6h_rQs=!t;n1?M+$l@{&7BTb)nq$ZD-^L60sbGF7y$92 zXPyYUTPDBJBLnTHxdMf3vs7m$-Uzj)J&e!ti?u>;PaTJBRBVVMmRXc?Vh|G)-()Pd zaRGUEOTb}NQbN!5Pc!IprLi|p}~ZLvXd6c zU(p*kE$<%@5Jk03Lho- zY?N$y&vhApRXqUIfQ}CbMmXv5t$Uq(Pk(I=D}>Xrsd^zth#D!|{Ctx*{BJE}A6y6ygtH)FZE{gBtkTd1v0j2n0Pz*Qw zXSyzAnib#~BidoI0jdmnrpVW@r6OWOUBOTG`kd;kO1IQ&t-lE%A8rBw{v%ChhxPX8 zAlWCn^31|Ta#-gnln&&3!Yu0Z*(-jWTWR3DS^U{`r)0!CS{RCk8 z#wZCY#LrA?!c(XG0&RNh-WpLPzPnzwWF+vS{A8ZdNuG^C=1gD@dET_hEEF8_v530k zwm;KSH&6<8EN8~AD+{#P#A1{#^J7^MXcP+;SS0(j@>PFWphtYpI0w7`?L|OTs4y+Cy z=0j16MRLn0)Tp&8$0A}iRyJOb4|NSGPXA;1nmR@kNRF@bs)2qZ`+vH3RrUrlJ$+5{q_j}p!+^vFy3=N>C7Sexc-G&dKH7zz+ozHKC`#18v7yBk|wsdu~|EB(5tMp%M z;`9GE@;lG`e~~6LJo6MRU;knx$51{hRDSdNk`T%e=tK#U#0}QF-?lS&3qTYAc?CE6 z0Thl7aZp=_%23kZb<0GVXdD|;&A?*A50fj* zJHJZBD=>hh^H?XVfc=fd{@pOZ&jW~DQg~=A!C$-j`;-5dH{t_?ufvd}L!VODNNSYS zM{#Lx)pnxveaPJGn0wyS>2Uj347lzsilolr9tL354t%tN(jq=pRrX}=7o3>0T9)j# zEU?N_8Wcf&O=>kRpVHAz2uH0BJ(g}0W-CVXrTbc^_MENnkf6W?mNy@p;z7zg3&#$- z7Xn!nm|TEV3-XV{1PKdFE;{fc9GkV9Jj%y{7EoVVMw?F-N8Ns5W^-{sD5}o>4*iW4 zIw?L}Zsw{wt@%RJ{K!qv3KULv1@dw`l5M%Z|B4&pem6+i@N%L$r*?!1&Ac&F>=i)z zN}n%$xGBMnRxv>N_tWjz3HN{M-MyeUow5E&KlW|HS9N^b zF=OXf(5LHn+E4JfE_Yg~p2NuQJXUZ@RI78@k`NzxocYzuL751EBl}3FgE^Ib4lZ2@ z0)l&y&+5K6HDQLA%8kz%zJE}YRh11g<@J`*oBg2ME`vrWi4?fl@Uk8} zEg^(<;@+)A5;HMSUzQ0JtlkW)h|4Gf^!zCmrNA+FZ9x9ZhIF5#Kyg0QX<4-6Akch` zVv_RWEOSd(kDhVVHAHg>XPbvbJ}t#!f`dbucQ|j^YP<4b3q(|f;;2YFUK<}eN#ROu zlAf{o423GZL9CE^4KT|Zv^fHdRU665KUF^XcW(#$6Xe@}p`4DAnT2(@6|g-v5F-Mz zQXiSzAG()syP{JY=f8FX(H^9z>DZBcMk?9bnZ0)s&#qxn84HRF)bmwls~rvfAii_#G8G7UMJu_$d02p-bA z3jhVGqnv|^sZ-j_uJRi%Ln1o34bmyxgMftCZrsPDkkbW$cjY( zuYWa8OW~D(L>)O>bw(cbv}{o+JB;}pbKQlW%_Zc(`f0T*D0}a)gYCgM%a;br6>aB9P1xu z8J!Nmb%vYcAHvCgQz>@9cB?Mn(%i3QVa%$Lyi4vV6QTL*fs|uEpKk`HVb0C!a_$mVWnV)nj~Pc64us(aC>}F;t_wa(^)?77()uk zOd({JY2OjxJ`P=~XitNZU-@woqJ2sCBKEZYxi>O{e6FxDK$_86fTtA-;{@XnPLS%l zLOkcBfiUC#4EJ)kM-`Uj_zsR1W+VL(SKy+i+WL7uXuWk8ujh5;{@n}1A-`CaA*}3&})gHw-OJ zby6PsIUG0*o2wT#5=Q@&6-^QX1D+MN8dOS5U$)E4fyAV#!2ASfn?v{;{wiYs_7-lu zk&uA`lY;IGS6*Ne#t3xfFl{^rI14Oso6W@`8B&5xZmW@m`;ogISw`#r3L^JvbCi=u zcUX#61omD)0-GTbO%jh>(@$|lBciZ`c8#!nR3$M%D;NyKOf=&D=b4en_Q3WLW@hKL zN#Z-9rUOpp(mt?5@$+oIJbYF;zg?v}n?=#bkMky<^gXC?AMD9@z8&5*8X<2RZZ;8; z>Kinc6hFjk|jH!9ZBg(^!4y_XvXg4(eF6al|>JDZ&kJp|_k4M4|a(Ao_ecnGC(r{gY zFy|GF6*;0A(j8>@ak}hqPY$lxh0^5>lLD~PzwLN*{I~3iexEJNH|K9o6Bvx}re_>t zYHgmit(QDM!|$nuK%^$1((>MxNa`9RrBIa{kM;E=vY&TPVadJcQ-Rt87_R#UkP4!m z&HO%urRi586ONVrXQ$;5mw2d#9adI?KAesrKv+%Hf-p~%m}spV>6Y8KX==aWq&N1R z7uGjw?Er#%lav{xSKR?LJY7Ft=vf?IkQ-3WvWAxSr1C zrigdfoJdi`*wDMWzH%UwH0#C;F6Z;yflFJSZC+q((vvUy)Lu)XC+&PD+v;Y$rgpxK zWl3eof@D3-2-I!dS0P>AKx6Kg0gE|JBK}Hcb+aBYVU)^f2>t53YYHOrG&gWuM=^G*YKhe&_@ zE3C@Ihp6}RPy+lGcblBhbhBUj!uzE7lVK>WPxFVYpC84~>L>H9l;(^0GusH)kC#JD zt=C`lZ9dDs=h13}lcv7;$jUf?PI#TNZFY7y;i}=Vg)AXfsq3N1nB0xZMub1ZvY)ej zt)60fehN#?wz>8kZ62X_lWbZTL)}%l2~89nzLp?wW^;P+ergQeXOjeNbHGr0vpYs8 zm6-+vsL*xipUKxS?f`=o$UCJh%`5~`3npbES)T7AkX~Y~O zW^W*DuCZlVryDJI5TGa@;NSx+F`hzqS_0-#_?jA0)(XQ1(9`I#S&AUrd5x+Rv=4ql$(CqR6Vm;F_r*6=etmv_Y2Ce!v>szI zOeQAmAkVm>qUke~^WMow7W^obvU2qdm@qTIKKpg(3o&Gayv<@~c2t5+x2LKlm6~$E zJXVp-`4*LXO%8AXV%aLyp@d^1or`fG%(sUdNQ^n63fMNmgs}Z!=`q>D7CbG z2WK>FFXu5Rfnoj7y~R~nQF@P@0iFD#QtlHD0-;E56^MM`w$7^TeboS+Zwy7BdI7(@ zf)YzMvr%_e_XGmeARW?(EX;^_)QE6qcVmR8IDh|YFbh;|=){Cglz*jm{OsDWjVcAD zcIw{(>R;fG4^4#=FEgCI<_X@LQS3?a0nY3SnO_8$7=znZehqmwt*(jVE_ul2(aMXO z@Y`Tw;zfs0;Yk=A9?7Px*bNGN^Ok4iRI51a-**9Mgb52V_vFwwp7%s2YWol8APW$` zi$~zLd7&NaT#n=k&q(7$V}xTz&QEjX$P2@+GV$CDQu%;lHWmhTi=xUjr^$#lljCz@l101sL%?^cdRe zB})tFtZ}@q+VWwRh&&~vatoLal*?{PUA<83JsSosD-}oNlpunt$?m#$b zN~By*zSY(aHdI1Q5Nr3Ug$Ej_VtoGG8){K5H_fM3(yY$^_0S+}hOZB- zX<^o&B;ps08AJi#E4|~cSfYM|&sxbp#Kw_V^EI(&*iZ}$%~9yaZ3|oH!up60O0h!s zF+s=D9y_+0#QdbiH9GK+kK{qBCc*QT=6ysiZ@aS6xE}b@a1pq=88@n*Ij9n!J`<&& z>bbhVVm{Vkamz*2sSZgz)ZTl{`T{i8QU`BDLe#aW1~hPwKPwv}`k4i)H-^)fu~|c+ zUoRn{BVv)GZzc8Wvn*p?L?17pu5`3QKwAyqoIqJ&S83mAWD=sQsYt#<{IaB)LoxxH z%t60mb4rhj+UC%xZbk%s(RFOv>0t|)g}kmB!|0Mz;_$5JIvIE1fMq>s#j*W)rUq-# z;kf>S3y!PT7%5atuK06^e9ngAXX|5}^2JHZn@z^84aM|4$>IqLg;L9z@)MHM28Z07 zjhjihUK2U`$BZ80hS$k}yPO)J+jKTCC_Rd1(%3|io-)NzVT;BdgJ^tI<4Xu)w)pTN zE{=?dbJM#)O(-~Ms$)V``+AcV;eVhCFnn*~{VdZq;ceRjJUd${61DcizMSXCuAf`f zgp7nKF+zK{{!<42k|QcaK5PChH>T27lI#aL7gmXZ3OMq%js#LXn_Q{(7Xx?40lR~A zK-H}9GLkGrd2{2a&_B@V+su9%#vA|yB>aysoB4vcvbFoZ?QZQ$`h(n1Pq9Fh&OEu2 zvRfTB&&L~p$<^rEnp52T;nrd%c-gv}NY_%zk!5srpw|Q`)Oy}7W8KeznN4q%2?K)p zEA7yMbyq*r9MN!`ge-9D=`jby%rbM&N6{_t#<>LPrg~BpYPtZByQUnVfdMFnwo(+& z`8wR0B7>)()VmCowst?*~3*4*SG;_mV%Vbo% zL64KRXGl)!vrTWLQNKGv<3-~PH84MkL?7T*^)s*AH9y8NXOwkG&V z*|3pIw?r4cM}8r;%=ZV((6Vx*k=t86tjjN&z*D{mF3wJyruydMV}XLs$JMzES_d$M z9qRYj!p_f!@1L%kqWf{v{1Bib*T>trd?-gbaPfFFgwYD$6Edi6M!%22`>` zQmpi8ml%4lVCvSqcvS;FW4zkCOCc5&9ad=hHg#(_<`@`m2nUcS*6OfN3=<6Z+v>R( zB9TO3`Hjb5ndTbrZss&+YEE6h021GSTsL2cExoLNep2|nW2F?oR!*$-w2>*>A7wg5 z0R`cgh5sT=LxqB!M6f)?X#MnK-P!RPSmSAs>q!iF#6TPddgvfJH9qt*4`pKy>fU+V zueRYiyN6A=7etc?E!tI2kx}9%r*!A*aeV4(y||yYMSePEKmfmWSCqp>#k*2!$;9;S z&n}usZRct>*IApy<>nd636&f%=iN}M4Ms2EOV5QMyi`)1`Qo#i8QZup%rqat+$&8{ zcxYhP_M%m-lL;qk+qoen<2=Ia@evub$>>EtubC>`clXQE)?s_>ygOj`P3B` zGEB6XaI2wsk0R^pS6<$$59yv)pR!YKek2KRkRrm6d?7l7sQ^7V+hD9dtFkYi4JA5u z+Za0!tzMWlEUDPcAb&iGe0^L~Y<%CUp&-)LvklGD+kDz)V&11G-JzdHA@ZbjxuSu&Y3k?Md-$uKgOyh}9{UUJ~w--6<`HH=awV)n%hrZzY zEs`TV@ac5e2`B1AK-nnmT&|c$cBq@vhKJ{P`&IDlxb@+(FSM8S+({Mu+z z_H1>Xlc$t0A>Q@&M8Q!F$B8YuKHap66*e%fqfPd0T$l(#27K=CAL~Sszc{af!8=>- zHBIH!XYl07h4kVf_YK++Ex5mbzX`0!QjLNJ8y!k7>UD#GB)lo5jUGuu-N$T*QYFbs zGw;O4Q+S5{_%mLySiL&8bW|W{oLUQT59ESsDhV3t;fm=g@%~ZNA?RBWJlhE`gN7fpXd!3th zXrgo$U5N@&5$i>BvAOCbd*Q;_fA#A*y=Wv zD67JbFH4tghex>oDq24ttWeASSL#o&#=MFzA|PDdaf0$Q6qJ~h1pU)?S&1r0as!|}_&7cNp^;T4fP}w7- zXmPg0BK?bPxnGI#)?>_0`{KP)1F}gS3T=;DDvzIz5}?iRL4X?RCQt6vbHdumF<&xQ z_sT4z9Z1-ijl(dsNuVT4FBu5!3qVUOcraK#6{)1b&6h2G(eM`? z1%qYr@U*D5Pe@=PW-Ea_>bh)Ft;<4loOp)Wr;qoZBj5GoFlOXDElyV#O4+_U2*|*e zBGr&X!8Bhb*dL_bOdT|flBN;J<=nJq*G;GkY68u$g{5k_JJ;Z}mD9)}8^UHj&;ZCr)g-2F&{m#%MWP4nbysvN3v3^I%T)FA&8EmlTG zpD)ON>NpnkE6%>=m+lCN`nx>#jPYD;zmo|4pDxdslBSCJ-OFt+cTL@xfnpBkzMvPq zM2lhComzdcQ3Q&*NM0#6OM1=7=?4$lRbvldn3u=y3M)+9Y`u0prdbYdnAuo292;@q zrNbQtEC46sPCz#)^r}J`yh!EKg!40hoRJ=}0u^H>=r|~1*#_0W+R(0m!&+T`R+799 zC$nPQOz!RlE=^6ooC+D9roQgtn7gfZyF|{U2&Dv(-15CvbBdGM;p)P3Q-lTg%rc9s?P^4c z-QuO`&z56qQ!6N8If^pgV|=KjZ8Zg)PUGF{1Fo_ui6wW}ZwihnEF;y&%$Krni}jx& z>14how2%qMJbwDi1ms4Xt751V4!xdCGJt$nje~~Mjwf*f3d&5dRTJPHn5)}bZx>>g z(0J7{PE~i=)7UiJmGYm)-rDVWhoa-n!3R&}^lxM;6Oc`jIYHOh#hvq>d4isY%xn1X4%93@4(5k96sVWf)e{b*$vS2D5QOp*2O5*=xS_M>8#((t|k2;?l+|Of4<`=3vhO5fNgF5u_FI96rHvAfW>L(`{wpra>(B_KLSJ& z&=Phx{`I%dCvrfL>_aSfWbc*sZ`nUTGe9LKRGu|vV?%&UrQ`lek?`>(TC14Pqy?e} zcWZT>Dlvk-qWtjw_D--~8S6*elQV!!BNN%)B)-oJVWva04`1dh zQv+W8lqpN6Y5nykx6b4*VK5mTH(+cK2e;4ef2MoQ8{Lnyb|Zf6_&+54f6l4ClEoCC zUk~-|p#hU1QK3Q)UvQ&_enbozyj5v8=D=zs1zQ+HiY2d07>&kRY)hXcVb6E?DwN$j zn?8l9usE^%p}NQUMdmOq?aF!IW^y;fn8>&Fn#$nBnI}w$(00YV8O5j?;~_V;j`QR3 zj)2b#zD~^L%t!6>+2<8gMnIxX%(m-a)YeG=yQ=n`HxN=-`Ed?%Mq_6S_O>I@e!?GN zV#CesO2oEksYuGk#h37>@PY3<=!)P(`JFxtOU@j+mXqI;J`c_Nv7;)VHVl6jofstr9T6u8l;x}=FWJ$BP~%@&^qlxQwevdh@ab7P z@1lgVk%XearJ$_Ke9$e7SzgE~56~?Hrkq#t8Lm+vA|<}4 zwKh}XZqYvW3pZ@o{CY`WJ`e?lQ zjFzI`(~@~ZDqK3}@+XI;F;gds%cf=l1RDUOX!L)_CZr(w=?>ME-Yh{P$qer=HuYsKB#nn=LWFO|t6R`Oz43X>KDVGZ&&el)7@wS;qfAL=o;p2&#@k`bK{F1u;U4`%)}Tn zvNu?s+L(cmt^h8j{8%y0)AfR0E{hB8&9-{A^|D8~&frV_#=5YT4`IvU;j$jTEdZH*U2?&)y1G5C-K(D$1d3julr+W0jvNuYwCVn&Vg+W0gO6K|FaoQ*M3iYtN1 z_?ahDrC#OBUq`8nTf|V^gWF#&sRgKL!jhWG72N0P^b82}E;#ba-B87aXB8@EXWQt0>RT5b9+ftR_NYT^YAu(! zmaf-$t&cF!)*ytXV!c`M50{^q$011!)G24bi9jla4j#Bl;Ibl$hCrzgFSf-4U6f;k z)J~e3riIZYT{NS-MlIhv_cU7!Y6SOlspVp2)Uko5t&a7dey`7kk_^{{iu+DC07!){ z_fS8-CBZp1=G|e4Z1FO&l68k>XveO;bITb$^=Yb8O$g>K12R=v*f+EMtS>K7S3ZWd zS}yN3VZA;n&D zZ@8j#i}<~q3bO7x#8l%R>P(TMWxOhV`nril=HM@(z(PeAd`m_ zH0Qv2ps2RM%2)_c;gMwEkt~SI%7J@~W%XjM%`k=#`=SoRE6mi)j42}a1=lpe^L_-a z`DWLuwY*v1{F}a6^zH0J3vM|0_|yVubvan{=&oV@?O+p}$7_lFQ_IQHwpaC~`lz-( zbCrdM7K4`A7M#b*!bZ#NE`diw_G^EmIe%kKC-NUw%BL@kd?cqPkCG`SFJ=I{JpN6iW`hj}9+c`Nj5L z&C4$?v@h68_t)aL2I{Ahb+0b+UGn0P(!9>JGY{Ae2M5Jbmf`q0?GP8ux(W=3jWq3{ z4{pe@vmJMM7?Kxm#>ZD2WxJk_jBWmRFZgQ7$!q7XEZovq?Ys}oA1zos%MCO&SHB$Z zMeBMuW$UG80K_66FY?d!GiDLx+L=tK05JA~QmRi5Y4%`tBzn#^^r;tZGR65u%U2lz8{af?~a z1!QuSoIpTrb%3nB(^1KG@0U< z9$%lKn{BgqW|c9mTDdCm0&a-l)_q1)_Lg}I99jHLxu=3RPF^5Gv8i?BJp z8dls)Tbm7wuOcnV${Aah2VCy19a!~i>^Fz{#bUel{OT*;W3ecG`cAQ5mFAaqp@z;( zp&(&wQX-^ZjMwPGuM5Yd*Aw`j$Z-23D=|=RF0-}R>?~6P#-UO?AW4XTn-MR7zxtCANiOi1`p^@4od7{_x>Z|C=tJX%WR1JUkB@qFhv3 zx_Jw2NFYeTf=Xyb4f%K)WCo}i{XKiSgQ@d@H7NlhHyBGH+f^;IjzdTbM9eX%NJ@}+ z$~F<>inc0I!TTB}9T(B0)CfxhQh%MwL%eF@xbX4{KXtF5*%DR1MXPq41&7uBILoc7 zQg8nwHH)H1vyP|Z`c+Jz0}yyFonfR`{f3uu!S2Z(%2^SiWz4C1*X;(i zZ3iahrk?zMy9_867b`i{I@IZFi;D>(eyL9gwi8<}ilbQEq?kq94$|qimzNjgDvDnR z7f4*n>~s?q=$p437R>Fw8{H>3pUk&sF?(=H@mve{+oeROr};&31g{PD5^muaCt8+Hq=}3ogjGNG=qMicQHeHamft%dORo|ZqNFc_xh59VfU&Tzg2--pf)1pNqhH>%g0+Z$+jN;b2Z@OkV| zuZB~8p7qKaPY+GfM>=2e+UX$t^hr()VYFdQ!aVhsWaZU8+Fh#hqt_kLlJ%h`oBf~+ zJ@jU4!pqknJTJ`R!K>~((CVH|>k}_czf0-V`{&S%;lkycTD|XiTD{>htWFP|+N(d9 zYwOmVw~GPNr1`Rsm+Yz=q>BVFk!KaYV%Lvtrc0iawa#XqPz;Dv&KnW0U7IroPiGLM zYlrhz-}C1Ei(k&jM%i7zyMHU~Yn|2hy+u`63$`o&K^)X*b6$S(dGHQ|o~07Zs)bei zMa3}w>j1ya?x|IGA~4j`ch8oyANtN0uP?-7)8Q8lu-(1Kp`ow4;Hj=1=urC$&6tOF z*^Y~|IytgklXvL$u3n`IC$wsnm`Hsh(@9T2Lj@WKeJ6{iO^(f)PbZ8G+v#73YOFg} z)j}x8IFVwdTF|{(58u0AuB_UOWsm8dC&ON>ulf%k0b_gZj9G6Bej)6!=z^B!^?iIf zryu2cl{0_ZVQyh}1M;-LKEAt#%C=b_EBk!0SBSoE+4YF$8Cro|JlrM9_PDf3zq`GA zR_oqTJ%5Bw*>`%wY1!A1RamgvfX=#X&ACIX1{A6Nif+R1IWF*YF?eXbW3zdd@aP%T zr*#i%GH>GidFW{oyWL6INWM!M)Z$pV=Wz3!30l3)(8Rx}m~% z5^>@KZ&&KLRcf;-KT^C0?sSiP!YP(IlX((3hANx6eYCZQp|SW?Rz@w43(u$f_%WNU z%Dzm4^L&+-I!qRJ2$B;oBFpw`C-nG)u!ERW>&tMbwc=dL_3NN*zc!rvo>-fG#o3K3 z{hMefL7dotgp6xS!;NXd<2m1n)+Oc?FBY$`>@M zAnTXiDS0#+)OcD_=p4ILm|V7==Xa96jgvh;1M(>V!V>5kxZQ%q>o;{vpViY<`{Cet6=EoR51NdiMBDZXTO}m%gD{dS&|d}Yq;c2ld@v;=vvLHY zxI5e=2s_lQ?2(!THAh@W!SC_(2}jFQeI-LYSdqz;OYa2}>Tc1E!+vM{@*vG=h2Ga0 zcK@2&Nl6J6*XzkOznH^?nzq=mYv7j!zf;z1w+NHq!ON0r*VPx*?#YUJxN6)6c-rS(4}~GT_$_`c+d!!w8MrNYkC9?Mm+-58 z?X6dM<|UT#yeA8E3Zs>wH0{7&u-(Y0=;6~JHmRMriy5%pt~&2-E}bgNzN_{UI_d9> z=Wj%;E_-(jMs9O8p-clo3@Z{hJmu$iCUJs$x$K-4Jgh-IJxG^tlA>t_*KJpyzaFJQ z=qM;o2Cg3Ohx54t`~QTZzFG?4ZH>N+9mqGXC zM&l?yMB?G0(~pFK)4>gUvlq^{zKl-U^NarNYlO)p?+=<}k-@>d5K=#&EQNMyw@271 zI0r=%PUsB9D(Trgelf=uY+_J=cs-vm zc~64}2K!jm10wd^lve`MK?F_5?3z+gxQ$RC>&o?+9yqkHdg1=UC z;0w-x7|9dXABQ;A>l+8PH=lf;ZlGv$GG|Gp`hY_<^@;O-)>XpO4ONOb`R8WH7y6@f zEs;?62>3jyuAA8NpJtcIgeTsH28IMdRHR%5liNE-IZq7}RxiV8bafR~wB+RZk)OX( zgZlHiknn8;Cb_2@8K%U4BB$QV0TN)tWQG(L7=kO%8plvZq|v#Y;nmIY2-&^Soz1fA zXHp%f`pynxFpwd6y|=HKQeokRy)LTy}#V5&VW zDIhw5n{_4+&xJ2vloM>$WD?$?mk-{{9ZDPKDr zsYxACP^yTt$Uw}5p>A(REqI9rcD5&gjB5B0o#EE)hL_Gv2W#EwA`S{CcC-NCQCcwr zd~5~(U;%*nhQ5b6zj3eQS9(L}W#*!g$6B|`sa0ufa;DI- z5s^o{QWYpwJ|*aqFcO4tnV(eVL(q~g5-vTgoqBA@w|mbGQPj{bqK@>->QA1(>_1Fv zrX5KUM*BJ7r8zkOfpAUX%+vstXkL*!FW6&0+H6IMqI1#0#bxrFEM|$Y{lE}??o^2u-L}-5;DiniV$E>AUpkKFOpYVp0W3mQG#&X{kYNONu#X9koE>kT_3Sy6*vK) z=2FSYgNO236te}C5>^8vxIBO<7hZz{sMWHyide$P(XPgNKv$9R3jboWxv2MM^8cZ!m?^(hERtS&bB-Vq;s5=VNIQ>(jQl#(rQFH!^H>- z!g$v|bH#w_ObTThMbn#mrXCuq+sYoy6o$dYMjdxS%k*kRjqv>8{A1O*4jQgIBAjP4 z-2^I1d||Q894Db{85&7df5B=w40wS^qbW)N3H)a!3y)XBw>_u84?}S|G&D5#*Q>$D zt3g~8Cj?JVPvs3tRP{!yjfh)&UA{p(p0u}<8WlBn?0gb{51g{x5HExCsjO|$-;)jEUSYBV@ zkPja(I5+^l3=V{K;2?E zt3!uN2K}AG;#iR&$%4a5HfzG{SWHd^RcU)KbLf2u41a2EFYjurvA}RpuH1EJT)&Ib z$VhEBP+a#LFjZ3`O7R6p^EK~2Xoj8?mY%E*k1Zd40ql*)yBAvBn~K;&m6b>aMRx0q z^9ti7*4Ka_mnxkH7RO#c{lHH<&9@1QpyQ$2VEgo?`XV|hw<&G zD_D=iF?!;_bFs|;sb)xkCPz^C*6k9%NDgyauEB2+$u6S|Lr*5M?CTigE1;$cATPGu=^>K!cLRAxWd z5;sig^SFIdp4@&PCE}JPx~UUl_1yg8m@(9s4`pNDT8{z{xNeGAg0bB@ftr;aiGmaW z*Bv4WX2X7u*eqJrGu#Tf{$ve+D6*@cS^MLh-#4YW4R9w6w4;mbAVz-)QC^8-B5l1ROhZm>NEGXveWN8lOQ_nhzS2tVgK-+ngS zxl&#rzQDu5Uid$0kA2T#g6#!`>u%_sVs&MP<#*%q57x`2H{P{>oHq_~;(o$+i=dDB84Dxk)5bzDfru@T}lH> zD8ZP<3QBl$sIKllW(8y-y|YS+ha+p#QK(fkVBBe7O2qI}V>7oD(4wxYQ#kl zFr!R=tji}POo>U&@%(2AlyY?bp^5oM4OsNcJ;7)Wf2AeY=82G>F?%>rU{e!t?hlI+ zN>L)yX$q`}jY@nc644Nqm<(rhS79TpjW#SeOO4MjgzhTG9uKTyU1uhy^^XFaCX$%J zAgOA{Tq5EtmPlh3ld@i8EFeQmDb47*XW%5Lk!3v_W3mu(a?(n$w3xeNqMR>s@aJK z1B~dTC;z5pcUK5XR!{g&hzCsR1(;z4m z3Qd;V;TwW4S83=AjVz_Pi>7ndk#n*?@z(Wln_$JI+kJoC>X&e&O*IJWnKlkR)XabJ z@(yV#SF%{RW^geDXBpZhV6l4fiyW%wGl~| zyE{W{&DgbQR4x0IR38=1;RH_1Vl{HyVuS*GlrWOUm`ob?`3SS$^}Z|eBSeqst0K); zfjr+I37)^%#^GVMHJXMmhiu4?hjAFSQQ2%v?W_*cGU9EH<0LjJ8H4iyzCyypJ!y#k zcHMkFXaU9S9Wla6hA3tUvzV6~O}vNpR<7Qr*+kgAq5*}Ta^zo>M)G3Y!wk)s1qBO; zM-?nEOfJocW5G+Pi#zhZDDGB9uK_b2yW+amn*bm!Br~>DEzK6;;_y0qw%>8LC{;XVekS&!=H>=n!o!RtMzHc>hU)dn=#=yvxO z$AE>OUW3}1N1IAhgRsZ8kR?U)OU&TJ!5T)TW4ywH6?fGkkLyRu_qMZZp-woVy4 zCnHB{TpE5fAGa@CvVpBnweJh9Y^C~{gRIety_p-OU_wxsTW&l{TeU=q- zr=|4+th{?!OhVj41sN^QS(eje+SqsH>vc*R4$={~D7P0(`95SU9@ zqLkBR%P7jbH0fB=A+JSU)0b;&y#z_(373|hj)08p52U{}7QBJN!ouB!p_0QJMXU}{v>7>3XrJR5RH~ctutX1&PpP$mCgxbHl+vzz&|_9&F2e?p&*jO zW*Hm0zJn<-6ckw9Asam0gRyveBNYZOvSr+9p)HUy*;GJIsx!|^?-f)rUpdaxwm4I^ zVJ@???IbVfVrlGjaHVP}LHvGY{w8b+81+^qDq>z;E=XlJj3c}tG0bH-(Y6Wx<@NX9 zdm9c=gH*MMyFTV_qPfTpxtt>)XK)(PvL48FV*zEI1|yBr6B%NcgSJk~jIsP&yhH59 zR1^o?sGwi>?(^eo!8x-PM4jQPY{=9*Jbu@S=LM9vu@9GW`a_ zdS|>*&+>eK4#_V>VR2A2e_#95o((|uv=kPek4JTN6%;O=_j(Px=`=dlFNz?_=F8&M zK*~+)t-%cZ^dy6`(6I85xUq4nJQY?ug=`!{M0&9~@Riy4ONoLlX(Q?q5ss#p5lFMf ziumYz?eGBywcEm^Q?*{?%aw5@&1CEyPNj9TY|YZIA_XR_GqnX`$_q+XUQ|VraLg8a zGdLqxudrhJN1bQJv|MkpAJlH!Q#g_)N6cOaE~+M9Z*Q$@O^SSDQ#zp%f&E% z`(%^Wy4$w#Y_UwB2(;XOPQm5yNqRWG42Ugp6KmsT8Zp)OQt^8ktNTD)ZZubiAXV9qZrEKqf}DBzTMS`i&&Q6)D5)W4+jNPY?iq^gJ+)d%0P6M0jQim<941rCsU$c$<%a5x#FXKZk z9lRVY$BFk|pa1nuWDK}2p zP07>IOFJO~3~f9+g{}{-J7{mRasf4DP&DxL@-bG|7in5+PgJN}od84$t2u+t3#FoN z#lcnS?Gco{oUlrM;~+R6lH%lX{sO`|?5o`#c-jB!$KjeMO2^VAT7k4XtXXF!KQK)}CdECUQmZ;qliDZA%s(S0M)9s*T*i6ScY zHWwMX|Gn3DMkxldMegCG`iedVJy-1C)&PoBKv@x?=`Jyx6PTPgzjy<$c z?2YfjYK&xUs{Kw{rP>8|-PPu+(tIh5xDY`*sS*8EuOMBXu8LghURr82q8+q6}YXX7^*-q-5Q zly8scN+;b0G!~0|X08}yN2=Sx%Tvt)ZLs@GNgz8{?#)IoS|h)x#~iQvT6--xKE5n| zJ0(Qp^NX4ilLdm?;JyK2(Eqg!)XZ~@hx$VpS*$@cnYvbfEC zjo*oSLVkEvmzddYlp)3VmCaaei7I31R7d7?>z{1-;tqz_-bd z<9-JCi^qsW_;&)=6_6207|2In*okoX)E~5*J?qm&e`Zf9nladzN1(=-6;NT))?l6B z9xOw~pF)?8#0o8mKp(P&6__~3-pm>6Id2yxJ1#?GpPZf^L5`Dm z6^c~Sh;TUps(&oQ2}6#v?Dr!S2>_OaC-f2vxH)S*phTT0;C|4OQGI=s5xW{4t8N%&zxLVH9mJTBl6x;_9QilzX>Vtz# zXzbE}LP`)xKtAR#M7^A&>SwJ$NZCD_xP48EQ15Iy8(JiUP-j=BPrcYq+Gc!YULW;^ zNFyXL9uVtj2%9%oDSfg)7AF6~+^cDorbT|&o=Y{7cnWVWkqXLet+xjl1_@rJ9o4)p zj4eK=2)%YFT(D4>{??~JKRY#`z@jDFG6^?_5DuS)396n@En8E-LaoHSe@RamL3+iM z$>b;O?gu>;SyWl;PEjR)4mBq#&e6Q~6A4 z^_IaQ5fs<`iDI%dGd~DMdof_tw!6XHx(8=am`<3y*W>)-sMORXFLW|Vh42XaV1|XL zRVkeKnY>&c-a0fo#A9$#=JFrF*sC%V(-ttay8I9pTdwsRa zWa5=co5*vN#MD43z@9>#O+R$kc3B=cD7dAHS)Pt@fri>lf(!P02WvMCacAb z6s{adDGwB|@+`6}un*sgQBMOeFPBB~oW>>Qoux85zA_mir-cpI)!@NvayZH6%rzP> zL29|Gue-&3zkmQSD4)rr#_=d(Xqa(x`o@hkcT(ToR6P*`LRyYzU5EE9&6a-g)m!YV>(T%)To2%_xie6iUTZMW_<56)mkTWF1nKGT3|k4sk(+AQ17DoljRva8_dPJq}HL-{BQ! zx!KrTvjtzy50{w%zh^TIIk_q77ZT%-8s~dPVy4(P!NrnF@|N>Tmr#tcigt9yir$;d z)&Qln3c+E|FG2x;~u4nyDcZU5PFyAa5OKF zkkXJ4!E+)_CYoH9X_jIL!+ap9uZ>6PJk0m_R`U5<_K?u3;;G?jq`(!VT}md*h{k0? zDZOLE1>Q_cw{P?mw5N@c^@2^Aqz*KY$~_AQ1nR%DuatnNk-_C;O|^QN2sAB{rTN($ z9@2Kh7cd{?gj0n~RZK5gZ>?6sDPEQ&Drk!+9aG zuImYjojwW0`|7*aTK#!-w-N@)lkG+p^1sGLNbvE1M^FGN20uUY^^!Vcn}u0pt%pAm z5#<6!iJm+ zRC7!bLB(I%g^DeaFm?bXHiie|*q#tXT4?5MlFaP}gfotR?l}DZ@O)5uhLF7zt>Za8 zX{XEMx3n}%ntw*+j5#z*oJcjuin3t1mo9dO$x)oIKXGwfJ)5CvcEcKSSAVa_KQS)m z*~yj^nc~kZjJUkWu4seM1;jTZEomSp%fpsR)iH})B_=)EclJb%Ms*3N%@FTzW8^9f z32H-bJ;ZfG`XIgtNluyCH0g1IjTLV+SZZ|omRLHG#fzJJ$o&4Ua-HcDkm1uiXJkG0 z@iO-g4Yhw8zSX7i(&!#Yzq2urui@0zxQTgdiCX5~S-J0pebn~g-TA;y(L~P?I>55a z5#=s>jE!ZiBi$WXbn5cZ@8fEFD42cGS_{;vAch-EogVrkbyI5?+Qswniew&)frOOH z`B7CxXAtb=2MU8sa_>}YJk|88jh32F+y+9{zSv=EXU`7Dr*tBvqi94~dohfOPZ94N4JKxU*1T!zVrqKkr`=w5*I^a|lN zdhs}2YenmPyT=?)W6PaYKBdXg6aH3ZJmq^#dObQeAj2Y}VOWuyRunN!55|#)iA9@w zp#_L#EG_&l6|SJX7eh?;;(XvlWyLwW}yrVQlcl!3M75rT|WU`CpoJRm{VI&$PGGN2PZ04h(jvyvBjrkv! zt72*-s7UbuCMF?ruoL9uiK*);Z>>*o6TIR|N`+2u-f*32p?ZT6(e-BEjvX+A`f)p8 zg=@5_habkZVBMOZ>})QQJ-lpQPF;{+#|CS42DXz4vZjn$H~Ymk#bffM$RP4-I7lwg zH`?GVyx<;7h&|9k5+*Z*Z4NffwWr)qGoQG2`YzB9hA+@RF5Q$?nNAOuyeJ)Ju`xIn zEAcx&1Oe}#V`)@ZZ)nFjlM4DkJ1QD zE`=A4va6;*B-=-4h>15pKjXPv;A@U6G14`|?j@ub>(B1$cSX~!>_RQx!hv`w70$`5 zI4WA!=js4Y6_-8sE5p0(v3nFJZ8;-UVPdP5@|rdqDdHvhnufYO0}9h{`545w13j^k zy@CgoX#nP60oCHfYPSdO!?ZPosXGLT6uOvIR9WOhbw;JKrVw z*unA)yV5~4YQ3omT_3f$Aa@!Dz|TJn$%qL*ZQ`F!E50mF_$+|I{wA_K!xR~EJn;B7 z4U^yxCMaf9)o^6{Eq8NHkUAT_y^`;AQSoo(DoycV&~iffG8y1x8>7FF1^Tt}yi;hv zv-3wmVa51Hm5Ct${*tajf`?MzfOz_Biu&Er^*3V21GWtO0+9!RKL;<6Q-qkD-Z$eL zOpOmslTjqUkSZySl4(njfl5T=1?A(F>3!Xvhi6fL;ykku>{1*A$_H~&cu#Yi$!P95H6^kX<2!l%{W+nx3z1`NOd50ce>V6upK zqJQz=Pt(Oe8S?K3vIuYRqJG(Ei+_UukE#CsK!68W(gyl}5Awl;hsP~q`9hfbsaBs2 zZt>pnfUzQo|KiKPsePidl;V~A&MO`xz)KB5%qH}wB_sm;>lkVX0VRPco2$N@!hga3 zm@8+&!t1j{}t#6t&jSZ83mx}lgGH6 z?QiP5)xc@D&$^AHLjPs1Pd4I3eyLooQ!6L#v7yv83;q`8_aAHc|35rH0(@44<4MUM z@c&aM{&OgAG7>yM{jX@Tfd2~gpMzH^f&0LY+pmfCxc~XD&%o;w_y5#3e_l4yE}!T* zikc^d`0v*G_jayO9a2{QQoysA(a~rB=LD~2z_X@IG4k>@r@zqij0~d#)Laqb{REm> z*|Ht$Wt^K^6Ipc>Y*;7(O>GqPQ~!sK|0jgscs?)yP^ByL@^s$jEkwqKny9a4wTv;% zSuxQBpmV-yEelMYmONzIAidQl`a7nj)@It{-`nL-FueFU}0yHpl z*2F^S#~x0WsTN0zz!%Hq#v=XIwZJcxu1&U2fH^cI@!`11*LEjz za(|{-6ZfVtYS)5GSF(FZkQF2OH}ibFqIq2nNuGG9-)i2rLq?wRqk~*IM1f~lvPlmh ze`6Ve0Kg6(Q>JppzHqnaVUUK0r zKZkuL!48CBAiU2B6OLCsKnZn0Q-b;L+IW)@bfDM_u0x+^V?H@Z<_6ffb8~B$-@N87 z$lH#txkUOKUQ-}=lAY}j1`HE)o;aVD1geY>Bn}su2_Tnu@`~smos1)%wh-2L?kMDS zY*)??YtlCKV|?=p$GU=Q)8~Odr|NF5%rdOH8dIe7*51uhFi2iS?xZSAdq8UltmbN>MgEU#J_b zHel0(iW=b*ZQ%h8c)dFlQA6{gZCh+m4Gv^B2Nt5Z{;H=n8oUhX^Xzb~YFH2(E>P+6 zm(H#)hfMROp0f3I8)>l-b_d%(X1>CIMF#d80FB*dQ3OfX3ccN>6p;`Ey_EvLy$KXD z2ngw0L|XA09AV5HxLAX%zomU4w?G_{H z*Xrg{WwgkGNVXa44AX0znMxTmBBpJ$MRoVwFhb%^PE>;4PifvStLfM}^oIBp`<%Zf z@#*N`L0cM|3PBBu3hkIyk#DLp1?JuD3<_4dw=4rA%&&WT@fXovUs z5|CkS@-Zt?%`ax>COK>;dRWn7)yuL&piIbfy1kU4P^KcBdBWJ^+NRK8`wU9XHH{)H zRO4G%Yx_wcxytx`n-R7@V)i2H+Wzd_<$ea&cE1AQB>Ot{E16-cKoT<__G1uw_sgfE zpM!tyT}oFM*~}prqnzA$`t|fA6;yz`2u|){h2LVeen8MEE$U0k{mS!dg2my4Fpy@h zV7ZE3DTNg{83J1sjbd^dtm*7_?8b`FKQCkG@83Asr#zTj5D8V5AVHP(&g{A9nsmPu zm605NnZQM7a4p_c=fS-M>o;lH@2E!BI@s;u%Y zV#j`#SYJOcS1!IF>2aBev0^`8V`tRF>PP=Pp}wa9Kd5G|XK6+f;}2-}Rcve11@>RB zhJ<|Xt5rJ}enTS?fkrx#(Ac+MnBOm{rTU5RxR_wFsa-e676u6WdcS;g?H|uX+i90h zz#|_8gi`gdzqnx?2=|1f(PtHJDf@GPc2OhfoJ{7)2VS0sC9{4_=e>glIb;Ch0*mC+ zi-fjKyhf^FC`)?WzOlBs8Vrjp{ISpob@_mT(q@rZZQ9Fx$VpgrII~c-8Lli-Iw5|z zL5W50rmS~dxFVBZgXH+u4#&o(T6N4hd2y6T|Ja={DYy3W5RmU|buO1=Kh9_q2ZSQ& z&c@2R*O!((5&FU#UCYP3+U7&Hzy!&XV^q z!vX$G4hf|UHN~T%U?;xbWp^O1-t~~@;^+0tMJ$Z;hmQXOATA*L%uz(G$EakZtazIUkesNsr%x6rjv11JlrubCr%t5&c1L7FJRgX*luQGk*KGV72jKdr z!bhub2Rq~P>*>Kf2mKR`x+|Y@lRl0LlWtf2R6gcjl0G^mLwHo&o^~65^B^k^n00?9 z#Hyofz?g}olLOQ4z&&WHH;^Dl2*sbpBJ_PNwWnFvAE(q(&wu2%!+%Sz0 zYkx>^hdLFE3-w>VTpk^0>kO0NeLcDy>&%B;A2X zf%9`}R(DP?hEB}Lpk;tvd0J?GQ1ijj8z=mITToLF)*YY1fX_35jZGiq`3r_eHS~eh zVE0}&_-ru_Ux)NVu4B89{#1!PdqM+&xTr;4TaIaNL(*p{nSxbDsopf}!>@CD&<@R* zjpxabxDgNz#zm`Hh(?pJY1+^D^2sTP9kbG04`F_D^Jnx9NsdJ?-uM@|`zH`mW$m6) zBrUv9d6?r_aG^j!aqF#Fh~*80R4>h+Y2M&rxKGHhnhM~huSAYz>VBP$ZxLmrt=+sO zg3-DZ3o%}Q40S)!kdq`I0mTp>G6t#tTt&!n{Yj5ueH2p(C=)-$ccU-IEXL5%fo4de zQYd`%Cu>fVRCT^F_2k4-$%oZ1Tzlc{Mzmq+ml*6UP_hx|^zQJ?;m(-k0~!hvk^RjQ z*cqqyc<$|7TPrPKYK`haDm4-gHrXLuEXr)c@Bb+CtCMzl7CxM>g$5&$ZhuxESl#Za z*1bMF9$#K4JD09!X)?HZX-c)c-W&`-yw}9;<}1mJK5ep$8eoXCSdU5SKsbu_rLc5* z0ew1*Pe%tMY&!$e2tiiDy~(i4AL-f(b@6#hcmPp!1%uuhUK5whAIY+@ezj~)j82XZ z{uO3^?U3TTWEIg12ni@ik|`n;mQ6M#B{D(ZlTzA4TiQ8zUEfLBA&%|9jMb4A62S;H zG2Fu~VX@nmYPegDs#u*vl!#APgqT(gO+w)(CH!43P2*6`o(3v;chG(2c1rY>Rbrb;JCjjg#gQKFB%!{2TsM8vGUX9bf_<0*>8_C_KE>xsp5 zJl9JY2`f-}Zn62x0rDlh;5jOBCUybd*x{6R4*KC(QiiZgzSz%VS9>?5F2%_t*^x{#nBJK=k zoAlRUYDjyN9@`lMW_qQirCD!w0SHRKKtc6BY7v*^%$Jr9asxg(CH{uBr@ zkq(-)I*aA!l?Z>fdT?6G&nnDZZ27$lxm7;hQqKVc;?d)A_U zDho?^=SMvih^Ypc3TF`*yW$@ryHi?tq?KOX&w?JkE`o}KuM@3-nWj^`;kmDWTIHG?0NbcK4Mr23Z^l@z=GCpp(N9UWL(Jnx z=8K#4_pBN@QLb+1pGMI6vZ%n3k)>;E>^8NF5$mWDrs2S1***8E(2rKpi*Lqv)le0P z;C=@XpI*8=pqlNn)3XF44aot)Y$&jZ*vtLdcXm}qm!_7U;X**h1mH!^*U1Kp@}7kT z{jIKf>Z0WuN|Fey92Ith0qpMqQ_hNXdqMX~l+?KSuq5i@Cgz5aIWb|6{`SD2V7{k? z*Ya2Q8EtJJpLYnrEtas6grP~iig*oAf8?Px1;hY^LOl8Uat-Qw9i%$Cynx`@jJg2( zGxNdl++$i(+X%CMRGDG!(f$U@3jtn*S7K)`7~8NGmuVUspm&~f#&|TNojh5s7A|Av zpzkq~ytlx}Y1ik4IhXZA!u(d_0GAfHYY)h?H~<2~zZpHOn_J)wV-4;R0=o z4EIlj)hOkixKk@KVlom(9%4M=DH}eETOV48)g+y8w!yB>#t*(q^LaM;4Ih}8sWoIK z$L9wXvSYZ}W|}W_`z?A=T}K2LYB+x8_8uTcp8i@TzAmvC{gJD3=ePLa-IZGeBCItZ zxc!eX%U%-ME>c9UR7buERB<|s)0w8n)te&qe86#G9096TQAQ7p?rf&ZGdM5ZEjS*+ z*?#~0Ot0XgK7GbtxCb0ft+L*+u}Ke41hilFH82|vv zlUsw%Fj}oO%^1*G>bwuz4uR*219-gIIvm(HU+e2@0l8VRU)r}YYV)uHy{Od2zQcSD znp@A2v;%kXT$?yAGJ6vsCk&X>Ysw8*Lh+!($;<-hE5WK4M6sbLDRs7v{14ob+2SY9 zj5dPvkHk$tYyy9yun^KNKInyJ5W(5oTYJve%iisqI%oYFMetWjU}vnM`2uPTM(>nW zi_0312F_18x<&4SV0Xm+L;$#xnvQS#BXTDQ08}hW(D}Ygh;*GMcX&HPNz%?tidB|p z#W0r^+S$6>8N(v);7`9#r@L_Ra!2k}3MNKu$6E>x&Ud7M@8bK8HO&=Q*|RA(_2@#5 zVx;g?-5k08lQS8Nvh_~IR|ZZ*9&;5xxCgo27j!!v>pLOuPB;k2I{o--@FF=V)qUPQ zXQAG`YuF-RHMU)jc&gfSTt=LO%3nQ^q1P2tEk@cc1ERZ+`F&S#i$g6C`fZ zxbVqAf9{_VIDit9q6~=JilEH-ik=ROlc9@R7)6Y`?jc$J>|D|8mNn4rWd7>MvqGyX zFLl8-%|e7%U&`E!?)ZY5>l5sE!TIMOTU*HRBZ9?f>Bb%{@XNy3!GecyOEm%EtaY@3y1~=~04iH+DPIYN>W+!AiiHcEtW=YmW zx@dAfoq;#~v4Y;CnL>K~A$W+d4lLYM6ZTB&}SI}n? z1V|N;$>%YFKvLzLvT>(j(N~@*1Qi!P*q`=935GgVniHBY5JCcR(9O&z!m!3c)(RPK zzF#{qvsE7y&z{Ag*6elcHLI#rfKkQJrUDC+HGI)k3V)&HVxeMSa+=Ng^u3d4&4&;Q7Z}6p2g>qBHcFT+ichVf= zR^J`Yi@%cmn^N8S6xmZ0k6}q*Xz5Oq@2p&UK>&hP8y)=1ukb3}_0l1`KE5Od+Rf_m zhwSdCBj>xL^AXz_H=K^utl!7{rZzzrAVugm7fOKB_6e_}ZCiBwOI)HZy`}op4_C5~(!dslJ6eQmqKDTC+uo8vP@q<+G#k$! zx9l@x;07FWA8$p=!uFXL@|i}|@nNJVM>o>dg+D!roS~m$b>G$x?wgs`sy%ce$U-G1 z$oyk-J^%n6zuyQoy4Jq2tPDP(tY^~ZtsnBSENZbvVqz(Tj5$$k$RP`U_Nee};^14? zy6zdiSf1UB3e+FbTOX1MOgN*(4u zGTRdni`K7yMt*8a8LBkf;=>RT;BF(2df|U%tn=}@P@g>=Qg(6$N8!=1{o=yRzouy* zd`13(ChYNkJuRnCRrrVCpKyXN3kzO{QUT`e)r%NG=A(0H)Zx#%J)a{;vGu=sw(_V-?IUJRJ5O@x55pp{^P}eBz;$3fK?cYGmB0W zBRz5x(o?`?MECQ401WFiJd9e6gzCMNg) zMd)*xpJ_~f;6*is!nqvHSa3wSQi2s@qPMzR&rcNr^8<))Ym0NY94t`;p5?fmBv${; znSTTMUk_qZT{O1Ou5KF%&ZeCN2S)g#@@^gXXN}LFFw}`j2BH2i_W$v^^&oiFp$iK! z1c3t!7Yupmzc}^3CiyeH6Uy)WeaRsH&%d$R|AX+RB>|rJ<3&LszQLbyaR5H28Z#Cz zPUL>NBB=io_5Vb4t_EldDU&}F_p3nxc((PPUab4ijQNCFTVy};;S^L_LPu2p>#HLu zJ~41~=M#`m8J@((rhO5`0rRrqLi-yQ!?1^l0= ze$#^ctVP`Myk73j|DWTU*Z@>}yzIDZl>ggw|0MdERQFaZr$HLDXQrr~Gi^VM3H5ee z0Vf9fKLu===pEumATK5+`IDXn?S3MEc)_r?J8PkLW=xVTohOkw*jr7_);8n+l%0W) zce&sPxBFMpabf&MXkdkQt-z|N5o4uF8b>CKSNxtHiE#X)B{6&+Zc&Zqs;-sVg*} z6_p;^_Ih81y?+!)#CU~~oI14%j{3zAnYAoYPuRfjJuB zr>3ns054Khl=Y>zp6}=J@@;-3cbz?wx2sNBv1orf>O#u*e~N482!Nb4JoRo+_h;PD z4(L>VO*l|v@HO!8E}%=zO2;xKIvW{;PI=H&+!`&;u=K&`jJ+xn02Q<` z=709cDgYpbisTY2stasEDzLkvY`(Enr+;GR*P#B=5C3_cCLn`os=peTRR&>Pucpy# z3nG+TEs=nh=ZFs043g;UUCdK#oZG*|hiN1*hI4dUkqRHtUSeSIFt(enK2rZiGcy(u0@ zQ$cAx(Wb4g(;0A{d{Mhe&ps0Q^U&ADG?c1)@1`O_o;)|G^83A6(fVdfzgFb2iHZ08Fy@Y7TVP-dqkpm=k&Nbm5A5mS z?i~Kl#K!~dSmnSQPdAU^cEVV_>#Bb+3J;*d2@Z!d=tpnBkL%rG5mC`>&!Qm;5cJ^< zzRe919W<#sc5K!!bLnCqo#U71vznqEe@%=v7l0D^u$cWzW$ES0zGjti+Ek4=t-Y}p z97QMuGa?PsQ2&0WSKo!YV{eI_(8{^TWU&Fz@* zofMG{YYZqJ)u2$DgDTr40tH(q{z5ITt&?+s@Wp!8z_!_8p@rpU18mO^3ou(MHAd4J z2+d_CKP|}x8Iz(QlgGWn%xaeffW^89fo+ChTVq=DHns%^(;hh(3&l8tcuP)JHb?L(|8@(u|XZ7;u>2D<*ZvFhA3 zUq$hsH4ftT`2Xs<%BVP+ZHokg1PBt`HNfBuZoxHpa2*Je;7)K!aF^ij5L{<~!JVK% zf&`b~g9dv;zIX4t@5k+5-D_2M*QxG0b*lERv%P61ev-V)!02yKy0#g!y>r`qLC$(j z%R5iCoe2m5evH}1vej&~WG6x^ihKIxf#75W%zTLSs`5pd(bu)QI!|x-LKWohY$Pl? zx~~>ApjM?1j3VZe3@7kwUWrXoLR%VnR=GK?rJ6WXMGAyRRL1jdpK#a>JAC9Sdo0U0 zjr|fgSN;xRC}=g>)7fklC;guy<0^h^D2^gRIL~Z+I^GL6G^m#FfrbXEK?h{@akegn zZ@dYNDKE&yjB7(L6Hy4lQC9uA$sP@wk!^!{*eU-YgLd!O1~%fIPetzPle#gmAmDJM(&57>4)ua?uTP>>m#UwTcNw)6PX0JEU$evel+{ z_Bh193?2I+;wLX;5=N#D#B}R4EX@whVwHtkos4yFnaXX%Gku3D(>gBp31kYnk)kUs z)L1Z?9?9{XoKLLjw(cQ_w71t>%^h>z?M(_0C%&N23*Ro5-U!yKui2Q;6HvG47k|gr zT~D{t8>)p5?1#pfq?jI*u6^Zul-s2cb#lJdk0RlErYd&I`|ji7a~vF;{ zjC7zAVt!5;<9*j^2J~BPq;gWX!p-?AgU`7aCge|QH7?sDYjWq?Sv~LpG+?^>Rbk$L zpB!(xK6iW!s&o7ux-H6Ou?OD=f3y5m&=To$baueKRW)>4JNj5GClr^XJ#v$rjCb30 zG+P9f3ZNG|;fEe?Wt;b2*^H?xdLa*X?*Dm>!`Kd&0 zjW$@T5wDI1uQUK^$K9zC5%f=yO$*PnQO@%AlRvkxiEs7+=6g3sy=%H>eUc*cjldbJ zCeYf{7%|rtoye7h0ZR}{PR9*7p-(Guth;?Eg2!`_t4lfJ<2_`#c8ubg*XM^9#C#8a zX!XV|IG``RW`hPFPP%(NUTzo2y?x8B2O`vyG~=Pu*$HhHyL)lC{|w2(ZeP%r+Wny& zx3$On*WizwT2~OgarQk!_U?)nbI-BTd-Kszj>($`G3zx)Ql@c}ZcG<3$xgmT>&{oq zLuK?I+j1wbQym?Di}6wlAb~#dQ&%8ja;?Z3$75C_|R zDVQv*V3jG;o+J?F9-K<9-i@S}-JB+e+3I?`h3356L7ed7=WQ)PKA>tfZ;oK>FIF^@KlQx;uqFDMLaw@!Z0|Xft>YN zLN2#oXmQRDwyYE@&(j;qLnoSd){790a@Fkc!p-ar2n+r#?( zB3~O(Vh5E9wAs)nkjjfl`kSkAfh0O!lgLc|*xPkdOum&Ih1Xgkg~-hezJ>cN{vQZN z^4Aaank^fBebQp(w4kvJe$e^uB(aDmXRG$4`D%ynS5C7a+HB|XR|>glV!my(xpf>` zPt(jh@U!`|qyd}544^hZ&4WT9taxqB(aBk|@s}x_$om?G1dkiH@h6>9H(992# zx^RIAH97=1er^52es`e=^WDe07pX)3n*BrdgM;*wr`_m2^}HCi`+)x9O=?jgZ1kbM z(t|@R(QpKC(?d{XDxUS;Cv15_Y8pO=*4LORHF{<=;(o=3e-cR>^M@?B^JIJ4thMOW zn1fe-g-r+nYahe(a4}+XVnR9;d0aoj6A^>36$nTh4wi}XOe(u#mYF6B{O#cTe81>% z#P+s6sOIVnxR)BShI~`#1}syVOiRw?`a}Ejkh;rf3v5#DPbLf$W`JGG)uchoA}ys! zgK{i-1oL&E8nan<`uCgEOoA9Jb+o|AGrza*6G>*tW~47zRupRxtQ`EWg}m~dH)80s z%N1TUb`OXnyd8gEI2BDctx>}vgx^KUS9!B$pjJCLeDNZjRxnHi7uoHnEu0>PVQ?dd zmZXZL^?km0sQ%XO@Qp)63|YRlzPb5GEHpdS^JuNgI_imddMVM|y^=k4qqo!n>`5p+2Fg zEfW+RT!D;YO?w!KVNHjrncR=d&LSFBMl#X%Y@MR+nbA$poNoKRlF#je&(i7@3FN$6 zLSV)8uskM60Y%4I9iZNtd2pqL>)}<#`s)6Q0mcRDdi=FZ5pwcpQ=C(fu>WAG;!O>J z4+tq6^%i=!CTlr*JJwoj`UbaoXzGV-2l|aT*_P_OBlDVo-#w5|RX*=fCB0EnMO;Vs zC>P|y9{)6zIT8spOmDEco!}h3!s?n^y$fLGhz{|Pr%?WS#wf&V@%#FBV)VY=cNQA0 zV_W(2Lei#rp~!nrub!UpkA@2-+_Rby#7YZJ%Uee#WH?P#QPuBNsXtub#(A1^xM$Uy zO?@4os11F*NKJkM!Qiu0HA$ocSdz>nyM<|u%`_u}{OVGx%PX$rU>T=!t+E7jFP3#1 z^t@p+$=oP(!$uLy)0sXLRA=ChG@^(U5>lMv5x?K!`SXkVN}T~`OD``tXqHgC`40{h ziC*KF9+#q$5=Wmo^&DHnpCK8b%67z?>k$xNsg!rwdtFvP`MOv8l&t4XhOSjZS{>)@ zy{hAz!!Hc0010auBJK)hXgr&6GV1=moeMQfI@TzRsE;SuOT9$JA~czaFCjw=C{t6S z90@qmDU=WHLQG3tH>G!ylo~nAqopcRKyR@amS%JC81BKShN_A1T&kQ?@gkA=-66qq zDuwN{>92>sr&Q$PCD=+2MF;bBZOz5y4k6XGzwpV87`7dPgn416Q0Fda>T<%o1g5zc zLkh@-7B3vy{0Ze?39`vL?+KG1nai%t8Oat!PXLTq`?-#r-KBd2%7m5x$P?!>7lNNl zDq|-xjg3Q{uY!{qz7(tH014tR@ed6vI%a9an#&=@n$0VRw1qqZPj)g^gtn9}qGMQx z^26WT&tThI&(jlhN>hdym&jES8l7%N+NpzW5%j>Y%A|jB5!eu8Ru1>}!pk&E%%=(! zBEp7!^^JdgPdvV~yrs=fNvVPFldzA4YL84a;_CEGV^Dg5pd&ip;yxAO!+9S4fSKoD z{Jrr_Zze?+ufIIH5U3sQ#DwYX!Ah0x{Rmaz>-~6gif|bdfK=$o=M$K_RF!P>+A(SD z)oM<(%c(*GJu~L%j;h)BJi0-K--i7}PBD*Z3+S{!Mx`;8fCQrgesc@;K% zLAEbq7Wbko47&KQ&%xYv!3B27x#NP@OEILFo|?_ zkfrmTJX&47bJ7qEYj`mUdWqAeIi6n8j$Re4{gnZck;n8$U;ki>vF;y6CBIB6NZ@%o zfnB@6Y)&t(3!5;sWy5 z&xSJ*uA*HV^y@TX@6pa+ngCVIh!V1<`|!<~24TkRGpukF+cfcaLuyZb+R+2(BR;>TJE8T@Pji%vo`|4-SEptZ?0+bWhUpC5oRq44-HXEk5yh|l^3bgl z*(p2nT4uyGs!J1Apej9J4GFv&S5g6G0Ny%!x%Wk#6C>GJP!@;QT znw|(Hl--67Q)fl?-M8@$&!`~9_GV9PO2Ib7A5jGQ!^e>k?Bfl9oOGKh-DK_x%=Z#I ziPh6fJ{5H$dS88ONMHSa7B>>u#i1fO)2l!%{E=ExO3HLxSvF!4*M*29sH8slngwuj zx1~y>_JCGeitNx+OhsgVx+qopjsc;#lAzaUxJz?mHKgK4!~`LQn$xLw6eagd8k#`Q z`(T{1^aiMO@X6zc;}!FaZv_GY{y)DJ1=|hwQ;+nsfnB5Q1SDRD(r~GppKJT9brD6= zpCO?7I&t-nWQ2z^_RCTEB+3;qG6vD(kr1OUht^_(Yq~>Jbd|vFRXcZWD^1uoR4vVaFb%h6}?n<07EM|#7K@4l|tpJNeTYj(G zV1t&$g-CQgMt!4BvaKSSgw`@8afa8_3q>9lVAd*%6D6jkC5Y9p1XRZKyz(Ix(L=V~ zT?+0G>%U1aq1#mbrAY{-i-znNZo;^S<~-Ho27$5)(>D3x)rA+yNY(qM+hM#Kc8rIyyK^?e;AC7xd%7q9N`6{#(iFRFxGB`%E)N+#O9EUHo6zR$} zrU4&fsHpf$5KvOS2E3p7bgW2%@4+fd|66OPlWnW9v<#4(!A>(Ba{L2}9}X3p8YO=b zO<=<%-VYqe2|m>RS$J3H_c$rci|E=?O;cTnk+E5v?9dy0dE{T)ZZDka-YyF2y6#a= zGx1hqRZ$-IxkB%Fw0vpe<4$T|`AI%{du_%4q$@o3w124DY5!2_WW*K``>$}+prOQw zhF80Bl!8$o-jF98n7Hzj}c~xYJ zi?qhJ6&bs@K88{HPqDkNY{Gi8r(cGp5KVxKr|Z9;%fK7VR1ibg(r@}kekX5t^>njU z@rY+=DQ{%I(cW0C?gtz9+=7LqeD?2kSp1S>c3hap`SR5Z;i$I9HG}E0@Z5T zB>$OfiIJ4~K$xpx6uX;>2>(T`w1SERe@7@P|LEELHBUleI+}!X=l0&u5@Cn79XzXF z4)9kw6HN$>Yy_6^=&u{4wbI!LMGY3F2!ZyS%3uo8v1M5g37^xH3RCKO;v@UpIo6sT z{Gh_etKBOU+AYg-5wM+ckhT|{v9N9|dVM;zjn#wVUs*H~bZZdvQ?s9<@X9a#SPt%> zi7j*SHsS8^#i1H!@l3C(utESDB#2=Dr$`#1yaaMZHTfqTvZBhN8%!NeUQPr#Kav># z^P;kmX0cV=?-3@sO25qhEineoN4JK$j_&a6%*_U;(>%~WM=n*>$k=(3y#1H<8Wi%3 zRs*~k^BK9?JoSzqXUsi1nLxoWyYoqJ;2&M`Eymof*eNWw60_;L4={2b3Xi5QAOyE2 zjis3XE%Mi>NancW%|UZx?C&anub=L&W2+J3pF7_e+;#$bw#mk144EPT@kNi6{$1kV z75S%57J%Hq@$?Ai^G_ltH}_Ky9uFiXwUOcSV>h#JncN5AGGI;pO+M%TBoe+2I3`8^ zVaWeGCDfH5Yqon0=#59_==LobnSoO~ZebL)qtg`0ueMv#ct67p=*U63`mZu{b&R=h z#?8_5DOYqtDl4~A5h_tNc+j)=Ge8Ky4^&(@(*M;0+BlmUoAm8^ zM@I!Q!4H+=*oPlJ@PCjH7Ep5gc#;97m1w+n@9O}Dv5QZxq*we^z+GDZIu_;Bz{Gr_ z)K-PJtRBaS?R2QYqc*dqv2Lk+^`zRkb!jv2toEQ(0_)bT=kwSn^u+A!ul@mkoP!u% zZ}U9hCLtKGjDf|SWfSc8-n#qeJkm^^k9Zz9Z?nhyU4kGf1pbANW5u>Mg4&LO+-$iy z%@||sq16+(t}ZB6Un3wqv{WcMlVvHKpk+w#5fT4)mmSVW$oNXbkvLEKk0hd$hw|#elu|?e zjxuTVCEwKFTmJ2(CDt|OEaYf}*u?%NHE76W%pe{$9&JE-n^NL;PtdA>AQRKgaSb>p ze1M4k?bRhB?ia8l;x^r6^!tv!_r6ID;%jW1+Bx?8DhUW>K>l48s+7VvpyIM!fmCk| z)8Dkr&jm^@-tZ}G_TPi}=K=jkd7Ow}V9K4E1MzR#c<=K6yJontXFZ{z{oj1QIV-Ii zRX(#E_+Oz5FaY(@BBMNzD z4SGz})NR|Yv!2M)3XNJT!>7-=+{`_nc>NK=+8sGffZCLjW-Jjd72Qj|Cjw{RMG3b2 zc~xNs64zH^(urO;`46@%9S+y0@6IG(BJOQ~{WR+2#f&SCZ`EqVZNm=}IQE4ADzx5X)#w?~JSxOw=1{CI|Sqv+6?X+0Ft7@uX`C|46LHAVfaqWMcD z_=y>!&J{-W5CXn9B_sb-6lXCiA`n6YS)G$k{Dv9o(phw$7@R&Q--Ixtx=Unp=QSEZ z|E2V43LBYChtl6MO2i{@eetR&V%0^3K|j1U&hKB!3l{g#GkeDaQtq$_%LG6BN~f_t=_B zp!A6pV{$@F8whS5AD8h65{cYkX8!+nKkc=Bnh zJ}~+5aznS^QiCIorhZ%CBe&d&^VEJ^-C=;d$IA%Q+Y=a7b&MYbmmU%fH;`xWx-VX3 zW{aHuw1vGBc3k5OF_6En+kK*+(!VY#LVL4m zabjbr1mVJry5}iL(_Ucx%)%iq$<&+eBtzM#FjsHJi#q|?*!XgBs^KFG3%{FN)8p&J z1@8gmfD_suoS1UeZ$O>ddoUBk%ts9p}G_riQ z_=acF*SsZF(6@D~EcXRyxMw@?<3y zp(GGp4NU4v-5SSSXC9~J_q|GgS0b{u1t_3IA%uIdj zxjRW{S~Mg1{yY@8z~lvy>r&B`_Gv(V`oRAq+c?nnyc0uZZMCQtut$eysCkCrp7BH| z2|B($iV;n}4GXtTtZ=>Fg;uBtbd|fWlrx&Gdzm{N>L*_ z0wtF|(i?N~yLEM=*ZR+2dp+fo`gH~Xy^!m`R29s<@QuM-ejMLhd50S71ih_ z5EM@bgkU67qZcjAQ67(UxkTXFp?4NBlq(2v&dV)?w}6CJ(&KUH=^^(gfLc?1rCro= zsd2%$mOUhYfa5;qI@#<@Llv6yFAn-s*%=%4(3v*I?RB;I$9mJFTHE}wUsnm2N|LGF z6>Uv4xQH$IJQ!I+>(ve8v1Y`Oz2SRUR@!v7XI~;QLn&%WHQK>W{Fro)pgA3jpxnQv z#Kk;~_CPg*j?4Vjd;E+B4M02-nd&G|Yh=NV91X}F1KEjO8+HZKTy2V^qEM8Eb--(? zdi%L@qv}mIeGL*5$zi0yLz&V=nt-gWR$VFhbZ)d=D z$adlG5tKCI_^bb39-RZi5lt>smT=hoE&%N$Q#ChVoO&%Dd+3-mDCjx6Sa?XC#+p&3 zLTIBmM(>hZpWs+q{89v+FD+zwm1x6S0J}&}YW^cH_Pd`djaOH|c>~)vchC>?(56 z?_t3-M0KM_iFqM{JHzXcJzESjH=Z%8{@w^^e2}>67=xn}i9Cxh9}F8~+9OZf8Y~zA zF2)E3SzZV+#cbmtn2D&WJ*t|^!ra&SaBJ!+hE9{$ zB6C=r1=wmfMXCXX=y3gux50O0KB<+$=;u4=zG5z0(I#V+@NwFzyi;#*{iSO;!@Fz) ztHcM73EnqY)`;6w<+xiQG-FuYQ46iCRtr|7yoD7iMpjtY59&IrcI0M^r1&H@6{N3A ztyL&`y_%wGVY$vVrpY^#A1fx=NrtK7YI*FV0aCtkC_qGKuJEd-o0 z+#8OYAz;?CzS2Ks5(jy=!ho03?6sJap2gIfIWEgwjE?rjpvq`DEW~-1lH!t7^$AEu zDT#!p#`SK*DSn6>86zQQ?8g@^rMlt_!@ZOAc8j; zfLyFI&<-PK=qpH5u0Bi~C?-Dj9K{) zRzE31s<)K?)hORcW2|UF4ykX1UKc(j8YONbz}qnIX&Y^FoITF;D|=5Mv{iJ;vMK9eko|4L`9JVV* z)FZY;PA*`8U}3R!wT};!b_bo1LqH%@-5RXWh)CJmA8MAjLP&u*R+|5ca?<+RJ2C3-u4A7lZYUt`SSsc9vwtcUu9 zsPA=JrTc{IgX&{L)U}}v?1eQf?(3WdIY+o)JtA@RUd&xTgkw9lxt0>wy^==oa;b}f zHTKd~Pa>*85@~WJR-;MytP+WZH%N|UB3c*yHgbPaSdKia;AC*ezDPBLU!6vEhct47 zU0zZ>pdUBVE~{YxfT@&$jD|1w%Ytl4ZSD{_%WkI~PWte@^kmF*$mi=+Zp`rS7cWGu)16 z-Z>I8w@1=$14V;A56@1eP-AaIn5bL{^+F&9XEEBYRh`e-8yCCSiL%>9JMkjcEa?~6 z>cdPJp0%X6ZSfTguNLY8c4UjAF-fQPBvoDw{q3C7v_tczH;m zR-eWqk)H%Pen@sn-Vc(B{~+NLIdqgv-XW`IlP)BzXOV7m+7b4H8IO+SNd8AH%Fuad zg!88B){qz?=S$w2$Y#x0)RJ|Fq?*KE{7?$`#|Z)PG!{sd^!PaCV?%)HBO*EhVD%~j zNIPO59)%Oq|3Vn9o9v&qK#o9E5F|=~%0WIjF80j_-C%;P{QI_fg{5%5)JPbAn~^)c zaUm=Bfc{J|q171k6MINVIF>F2Wnb}VjTx%O9+bcrn0tQn@78(4P$_+Ly}c)tlx(2t z(Y^Ex-Nd59JyImHMeeU5pH_bY&^4-6fl-1+r?-8=2~@3K6{myV^%&z4MX&T;g3yqx zlF53;&1RaCE(9cOU`QdFkx3$H5(OgL(*u`=GLgsk$_xO0mj<;?9pS#KOc%37l)(u~ zA>abQgh2u1F&sfp%*C7FyjcL7PI5KG%hQ&)z!Olv&7xZBLU|E5wegiDhb-> zg(i~uOV4T`n2c<~LprVy0-j|9)yW2Sp-G0jwL78HPxeubI`rvYrr4X8OG`W=Zr_`l@}u5Fxv$3Hc(4{ze|x!{h&zim((>w zg9B27^H%WO_Gbx`nFhyR_wc{KNwkU}9b2TVhGqx~iY&hTOu>TyiLk*Wt9CA^oOwY8 z)@;$0amH(!fK(A-q6T4Vle;;+nXd^~#fW7ydbX075e9i8pib5$2n@_r7RUxCUU+!S zjJO_#qf!tJVSq`Ig&O&-0SqCjBdA6~l3q^s{G;tS0E8w~bZ)++1AqZVWOV~dH7(=Y z$`<6z40E;sqS#F09w7(EC)~UcOm@k(rY~^<4$?>8zI@Y6Q0%LiE_CbHD_)r%jW&-r zGov=3iBjTn(zOn8oHJif_X&J{I_TM9WtIKb`nW}cZdI5c86 zc*=3rK?Z)+p|*#C(MRsf;B&-b-E}$PnnA{1# z6^Re_I%uC>5}z?h>-<<>f0e0V`1sYAe*ba_QED<*fdmoL7pOo$!!0SVWFqIxI=1e= zpXOy`f|@6|_Etv-5zRPXDX1aD-QQqDcHG5ZX^|^rl*kuBJZ=ZDFM}!gE-q33sNY2! zBOWCa!GG4Gyg)%GZXJ4st1JHlV&ttrqf(1Rj&cs1Fds?7YG4h4+Vrr0d&xR9RlXI= z-I4MyP};#Gs27RJrW4Sl0=1Dn5;4*55y}g|>W-qbs#MSyeRBaPG?TARqkjzSVPtKA zRu?g3Sj`t3hL4LsQqM{bIRFaaL@XOKSdzir4h{_p)e+|a^2ADm#X(M!1}T38h)XEZ zTg`dR5c`HS>O5$zV0*(OrU$7{i*3?K(XcA4qSJ+7FdCu`+1fyOK?j{@~+H>bg2EnJIFhX3q z$IrTI+jS%+PZXH)2P9H^kHutFHb>a8_I4#`^|*o5cGcP`j)gJFh8}2>7UeTiTLmF^@99)u~vEM^eWl*3-Xc3+j{XNT+ zk8lWZQs=xzvdMa{pVH7X6xms*}F%{c7Us6p2^ zi2}f`990Y6aqfS0e;~3rARg6aF2yo@;MDlB>G1#rXQ;nodIzb}@0M<2FeBCB*1{#r z8ugyJ0}%HN{m(UqZhld+e*XpAlS^-&%m8n-iQ3k}2$K|ombQ7z`HUb0cGi$$TzTsJ zqFR;3l%MLzGpUG zD=ffQ*J;siL-a3k&4vR3JhIWL#qo*vzRwl@FZ)>qZqM3!tj5~?!xJ|Y6fmy7@jJuk zJBS@Jsi~OiB8GM4ON?b*K{!i!A_!~Y(bf21kUab^2>C+9heL_x3HU?Z>f&9}*v zrBO>Py12}A@!)?)UGRMOqw5hHgOBXR;Bk-B(u5=UxeY3jB;R^u%axOy@k&$MBm{~6?X8?B=0aC~a*yq1KdYC3Z^ytI(=7jOITcYc4Tu18R8 z$atF$oeK=CQ@j3iuQ=J!D4IMwM>UGLp{EmtLvWTBM)X= zuEj%RcOA5~(uqeSs77p{!|1hoie?z>*;O^QjIjsvNMP{a3Pt`H8ert<#+?VQ#=_y+ z01Fc;`O~Wfvz=2b>jN@VwG)52Ki14vZGEOi!(#_4W>;@H^@&Ul^(;r^5(YKVy|u)f7AVq zDI&YIfP5NA5>|$+|EhF#oiLVoq(zP@^Jb4W@H^;Adc@>~7wZ7|ytRNdo_ z8)~iIiCqVt*DPt|Zbt>T71X3j^#!|wFx1Ee(a1ODMk!Rvetc3n^aq?t-=v=>f&;)*t?=qI?A_>SRk(jXT z$;O4*!qsxU5S#IG|5?*Afa4jS-f&-xs(NaIu-RgPW4>gxx|V0gtpT=6SrLI~lSI`} zkd#wHs$^O1oS;IxS(v*rK+`8+99&9GKC4z}z)^UZ*1q50lTf^@m|Y%_~3rg`cZ^E##d(4catY6+{zrnM%kyeN3cLehvye%_ClU6LV z{&m<%XiGWXrsIfn@wzLr4B2>UsmEw7;3sb=%C zi406}IJ|Q@uRP>pj;W!qL%vGXo5$Ju?f}t9E!oDCHRV=*;?=TvniqefhAIm9 zFoDGkJiQj#DI>!;cF|@Fs+65G za+vrJgEe(YfTfxYsu=6lstH%xq=m4*U+U7JT4y!d?~uIXtxVx#c~(0&Gu!r4porPS z8QF1*4X0wb%DD1;HxVPyqCbs}aoI_%!D6KkX~Zqo8m2&Nn1`ez50RP&EF?^`J_YD? zGdI3`Z7St)kw@sT&3Ls#aV*W-vfak@SkGB@PBBxV(nXCwb0i(wmeBU6)LNk z??p=Zm1@f~kkjbzs{?Ih{J#orALJZm)O2RSoU(y;GwxbaD-kmtR|mq^L!x2Y3ZNq9t?jQ5&qX_QvP*)n2!8zd7?cnqU@ zJyey*T@5w6rO7InE2)P$Ywmrc8q#{E6sAiBWtCJ;Gry*h*c2Ea(yH2zaJc`+iHCn3 z2iXt=ao!!2TK{(XTqAEqAzDgtd_&IL0C%WcK%{xZsrZ9boKkfQEPP-gZfc|#=3PvC z?iT0MikG$L*bj}Waz>iS`)F06W*OIdasv72_x)M8V}MTjBBhk`STgmsQ^QvNFl6P^ z_XtGh+0>3LoihY;>-vRcs-}{+N8V)(3rb6ck;g=-MvbB@Bd|v5H8xT8%&XMDaY5N+ zi0}-IT!whx4wsRzL{e8${Jr~5>n#`a6}1}Dr6#gnOl?9LEy{>lCCyIEE}I`QnQ{1{ zLxlQpPNxc7hHurH&7U>Gd?>qRcJbz1%(X~}!8+*cR~Zt63k#lW9njvnp@Po_Spzn~ z9(Af9Mje#)LbeZUk0%z5I05s{G?jZt>^oI(^^L>`6e4=O;7Sz@s6NZ5gJ6Hw**}+8 zg|lJ%7N37g8s5N)I<4?;sIgv^mlGa;&1}aTaDqTz`1p28tNBOJJG<^hN6(oozx+Df zB^!9%YRSF=^1``#?!~3_YE70_c09IfU4b};BFp<#rO=dEKiXOzKr>NP?5r6Pvdh>>2B{ zCfMk9Pdd9~@z+7J>bsh5tf^qs)08c9p6ya$RGniI%=xSuD!a-82j4Al&PuF|h`IzN zg6u3uEBU43m?}->(p|qQfF|kd;ak>rIipSbTn;sPlCkk=10FM)tJJZqB9~vpYRL*9lz`9{ zgZ`22wRUxVo;NXW?u|7Z^#uvw^@O5S_?*2EAbywxR04;6;kJW zgj*V@sb~9~gws^I(#BH-ab9zLxA^vmdNiqHUbR|+##YA$Drs8o7g1mqQBfSkF)`?) z!ACO8B4L1jyAnseR=*`$iA$-IKWtY4G${G=1w3bG%fUi%XX9P|DCy6qE9_ zP9iU1ZwfxB^Db@werJ44TDfJAHdk40LD#PE?S>6+K+zKJlK>#qZs1cY1{&NOY~q(TyCKOL}@DHPpSn@1A1h-kY^sa!2KVmHoJZ%A`u`>rxn zv;9T~a|Piy9$rfc^ya4~CT9a8kxV*(Ofor7%j!{!RNRF!&joLtHaxD-nsWY>X<*o{ zvZ33lWPnezKTs)BdG4Dwh2%jk%CpsgB1n-(-TZjN`eSBU^U-WxM!U1o>3(Yn)GD3r zY@S+m11KP-0=QheO+E%HvT~ zE-&4`&E*<-w=)V`E~6i}8Hl@(=CJ~mm)m{uir`f%m)9->GZ<092|gAf{PYcmTc-(9 z-5Oyv$nS&hhH!hT=nJcw=!%ECS5M_hF}&V3q_+tM<(M%gdTizp$0P%QkeqE+Orz<~ z@{VK?Q}<8rZ zeiG}|9GyAKq4mJj@jYh{o-5jFMoIQm)FvUC-$QXbn>=h%zRSNWm}_yW)rga!SPx~H z;IUXs(Af5l;SFUu>_csf@;)-+OHVP7ooa$TD}o)cy6<#iy3(NOVee>2PWIdktFoxz zOa8V~@Xpnk(-_+H-H4QF)Xr|H^M0$Ep`rN&DYZ|m|HHrl2~8oZvAJM;QmxaU>d zKV6%aK-Wky85mr6t+)b_LB7KzEv?wvFQ=gn>ooQ7czb#d8ISLr>7}RKu1m(POc?db zR`0>RF~>e}MNLA)eJc5kDQfPcQhkEx4n6Tt7LeUTUA<6X#196?fMDbN42E7ZJ&i3y zqjI^vU%^CidY6U*uM7w|dxg35J_MP*mbh(4Qq|tK4sFj1s|%fOG;5hwPqoSRw+7Z6 zt{*!tB!O*wZBP(nepkK60FOSyCN6IX);4dh9R(?h#TXVkZ~xPQ+{>SVRr-ZWb7(&P zC?hG`fHsHn7bDy)>ba>Y0FoG?Brp3fZR&>AbRRqwnsxAVE|V=dIMLyjBl(9uNNQaN zzN@48?QQdZ@59FJ=GH7hj}>TYuSbC(_o0>D;DruDriPRcOclur-)4**0c}2@do*59 z*E2qn4cooO-|iCtPP83eUCFOf&$A;b8^2!NYu?Dp?{rhzK^*b4qkF_RTir5Q*2myB z+LPwmBpc`kLP?0|J=ZMJb5V6NqhpfWBL~iCyIXbnqT^VtX4`$dx*cZL8Q>!KAM;8n zm%KWUgzJ8AxzY@ajdtO!rQu@(S$v)?xSj8PfYH?fT}Z!)jqDIU;y6jFk*jzJu|@Le!R zPmImydh~X?vmC*tU4TMU7YO?M|^lGvN$!t*Q zX1ow-u6tW(dHsSgVWU}%nSRd~#xCVtXRMsTK1E;?aAGYMVl)GSm>aF_+_-&fYtx8n znh)Y^MVU-uy0K7aju~=wW0XG^GY~(YNq?s3j1e9;yxy+N5%sDTRe!F2BDVDHOQma5 zR?m~LSB(v;^oW8XSyWj6wqi<{@u87bs&(lQ(xnRvY>e61{oUoI#&gOXu|Y;N4?K@2 zj)I9MtwX*MCRMj}OoiCduV8fZJ{P`Z8uIR$`-Y8h70T+-*A)$f0_%NQeuEx`#q?2v z%YfhQK)Pl@1A2je#ZBnNND~85r?FK}Kj#;BKqrNVPRTFhIokoW?B%NuaTcc;4vUqK zoJHFh^`BnGsq$RJ{5<-h`RVW`zA+nPA=)wcJ0O>JN`exoAwe%^j%!j=`1i6G8gDdM zaiJ*E0L6u5*hwdlECm3aD*#>FrhGJedv6}ZA{jFTqUc_?m{dP5AjoATv_!d~V&S;7 z)haJ1?C?Qle%q{pm51vIGrJl^4T;9ZQiErZP$4SxfL_|7?pk$QkW_;Zp2ac3`N57) zNZ4(ugadO-zIyp^#C%btR4c%!Xp5*h>&}h9ixD?%Gq@*)m1ucpt-JP~U(D**pX-;O zdj(J&Lwt-zu)e*y9SI1+GTLp__PZ!|oD_fkGEasx^h@0PBhy}`jzy27cRMC zn4B&Pf;FP$Eory)g@;RCE>Jgtnz6p=)-4Jp<;3kFBDu8N;;JF~x`+oHpj&w?Qr#+0 zkXT;|R*mtH3M7pqDON(2W)Di1p(iRh2S2CdQ3HK2?c>(&G&pZHyxS#otb7-$%`6r9 z&g0;Qu+XTNDdQpqE_yrKc%JE*c~p(5ag+Q8h?s|m zx3?F@9%ic1znZeK#mzw9PZfFRdR@h1l`!hlWq+8Pk{i~iH(7*ol7-7?B9*GeBNHzZ z0$a`q;G^scxQ&&vwJ;dce0c2W-7^NYyWpWuigoA&umbYMB$|)r75eGC(3nJ|YBe<| zR1K2Mb#-f(U%lv(Q}7Zh8$1;ytQ7$6t!gMHXj^u{yS&KkiVy>*Rlb37i|K%)UKBM_ zd*qad0fVESA_iNBI+(fZg_ywC<2I$Q&^$NEBaAdd?g*B);0xeXNyYfleJ+^+nr!h4U1haZ0UQjDSp6uTrmD7P;msx)SpF zSd$K)-G(tUb|F=+n0gD3c7RdBCn4>Xwt<_c#xku`yU757BHR%;hrmhI_RS`wCR^#0 zq@hS5ya3tO2b(GRM9gT5o4fkrBcL3bDXfU3#O>&pafqnwW24sYgZ^(4BK*%t@NWD2*n@~$&eiq`F#4pU8jV6V(Q=8WMQe)Jw5=E^~ zoi^G%DOc$u6hFoX z3VBW<9#u11UaPKADiGpRFSI3OBvM&x{eyugD=MjzeE#ta?Xrzlqb&+={0eVejLe+Y zsPggEDV~A#COoy)Gp-42-PyS@$p?*jbJG^Hm0v*5FxXBwrsOKIer}1S=^-|@5&3&G z+yrjcPc(Ex0NjBZDdE$M2>R`XBeV?qHY(d1=hT%WlduAdOo6Ys#p+@vhOafyL&>kG zD!f^GG^t0+%h_WJJ4))h9z*S8;|a|bh?~U?9!g1NqDQH}gQHp!{MZt?Tmra%C}We> zn^#(QEk=VIWx|HVjqPtRkOPe+tgx+BNE-DC{Gnv|8725`)QFE1vwFA7YD73|6mOm{ z3XQtPFCqrELQf#Vj{&6h3x$RZtiDaSw>shC*!l{ii1aj*P`Ae^`XUXp6Ns$dt><-K z>yX?ndNh)RrW=pnCuxu(b8@cq?;B_LZnn0p1~_X9-jiEWe9@E!llAS~oEh&Hw>Xm{ zSh$3RGtz#>UyyPlzyU#y`CdQ8@?>Osj=zZy4lqEmHN-Qyv{wO9OEqx!@V+-!PV9*z zE|QJbDLK1c8N~2B&pd6)zt}T9Txm%w!Uu2?$n=E0hy(g2T%Ny;iLYh`LRMWq^per# zDKlEL%zv?e)^Xv(9n%a0CoPVe-U4}@WNf1YtY@`dQ{?!A-)||DUu8$j!N)^28 z0k{fbhg8cBJW$L;uX|;m{@lL=op8I%*{jKl#z9zE+ngd-Qg6_lj;{X>L(1;W`+4jX z#!{kBf7U0HfpX&SSzH<@!zU9Kc1*{fY8yvU)Jb6^~rYi zxS|>CiDNlWa>2@#F9$$aCL8vZ?y9xvFo^{D?tCGZ_WVG^nMOar>?)S!Ny1CW~=VfEL&bHzx?(4bQdaALVgQ!#yx8Y1p{1XCXY(H(1JZ7m7Oo?T7 z)5?(NC=KhU(;0TZ_q7F?=#j;=gB9wg1q7;LB(WnP4!4z){rUG)9e)hb27QQ8D^(IP zE1E`>lBE;dqiaK}9_znB{Qq+KO+P`gTddeJ*aK^`UY`Z4Vy=JR_CLT5Ey4F31;H!C zfUb&v5q|$&aDsbJmRs%pUi;sF{j287$57hk{C5K9Ck|);!{g~{>I#mcJ?w96#CzWl zK7yJNEPk>9N%b_>D*sLFcbLdOXJaGw!)J+5GqraH`oE^1v;B(pFN*WCs(KdfeB(f&e}|22J-mI8$K{q(i35V{XfaTbP>vJeyOkMbS_K$Mbk zby!^Q^JDM&vo^g^28O|6Fp^+wJe>=)K?0$&_obQj!P1czI;td1%T`%>-pp${JeBQcPJ$qj=M|`PwoMa(tFRLgIA| z_G@fp3m&ZeV5IwR=?A1Bg?O>Ej@sJVY-7Xb|10?l|DEMlP#2?&lSkPtw4Pv`A{La5 zfMzsZQK4NbtvI?q8KkODjklmqiHtAqeJjN7%bE@Fex<)G<(H29(sM4_ynQl`i2Ze5 zK487zXRww~w{MGdml4L|u0Q*q*9m_k!q*S=s-^CsQ~-?nA_HdNZpG zb560ElC18^oHtWRJsfoz2H0%xK5n|)5ETF|e^F)W% zmKF%0|N5~LqqvUOza@xId?ne&4aCdo2X5ZN)IY2T4qOP7FrT7DkN~_9_V;bj)kZ1b zE}aQ@lWI5TMK|K8UZP&p2& zMq}Y`x0u6>t7LSOc}8B+8TYxr&Z^;kjZC3zyVOPCHMlesc>fs@a^Dd;bfq;z* zDO?nUFLLai%#s^M>1|R~O)}WLgT$Wxym5%|iMZ03=umc!1uPQH>LHuuj0q0 zHS$%a2D$j%3|Fu7zpWwvGUx+Gr8RH)|7_0v#}Vz)hUPXpc$Zcrjq0e6S|p-gZaZNG zc%%t;k_C@XmJ2(Cq72o)O=}Mf0DF)!anYwO1oL+D4qIwUso9O~PQM{SiJo)jhwpBw zOk(F8`JhfJz!r%-5LA~!`XVzZWTYr3>f6i1a0fnb$ui?VD$%q#`7}*1+tlGzx>SkS z_=iQtc_VDg2h!b#p_d%p7FE}IAmtnX@3#PQVRhQQFQpVc?;Qtu)r1+QfPoW#XX<#; z{HCdiaZVi+jIvdn$7Meyv^Hdv;<}YUTl(H0Sq@NLq{MO>Ww8gdysxA@ts>9X?y?*R zu~~pjb5Adf>9GaOTvTC8n@1w-)xc~V;nzK-ZL^>+X_E>RHp};)J$sKwDZH}#kG8+9 zovk&I9b&E+9+mOTck?UDVjjhLPnE|1Eon^r(-4APCRS-KdKh!ch{Am7vZ=abNk6s8 zs_J={WeHG|TIW2kf>PK)DkV{2&-R9NN+W(WZcv-y4)^jkjAP)1-rbJxrN-B{ZKacr z^^`9p=Ctngp+Mn;559pkX3OtD;cLRJs=(Sn-;Lgby zFbh)q8C9L;1v;tdMIElOE6jR#zODe+d{u+iy|n{g$9U0wf~hMjF>W)@nwH>2r!DHm zj?;cFFN+x{Xe64^1?3_X#FL99$;r)p9JbbMwv;kR<0%AXGo5Y941;2<+wT0kX(hG; zAlaqiH`hKy^$U{qXek7B=Pc}0R!}B>dAxr~co=)bo!b&$GMQBIe&K-`VThi!wsUT0 zIDHcLq8AMHE1iBIE_&UeC(!5pa$_bclvmDp4t<5$#^551P8tzRv=P0DsOpf5ZT+hm zEa}%Tw>^yvQppf;g>({xT&>K~9?V&#BoLl&l3ALSH_7aA9>iFc!?%iqmDj+FOh- z1QhmHqflvoQUv7>A9IVezwnSp_2sq4t}@ABB#t#_F(*%6L-j+?GlOK#sXHOoc@B9x?Xrxs&?*&k5RorwM@`NE&d zXvvo@zaSG}q*-RLS|Gcj<;XP7!jjY6fhw~VvaX~Vy$5EWk#hW&$<{}*e1BWNJ$;_| z9aCP%?E{B;Fu76=lGpRraJ3~o7bRwnfz$EP)F}h;_5Bv!KpXw9IjnLa0yUeu7kt+9 zl7ZNK`7l7+8qv2HJE7xu&F z9tob-9txBGs^9ix7AD=SoZLfB(bgCbzcKQ1Bn4CS!GL;u@?ds%?_NC%Qzn;w25XJ* zAo{ho3~$^BL#J~BI^0;aRXJQ1=W?$HuE`P5xXu$nFpb1ek`+k0>e7s@$ty9iiAA<; zFH{;~ll~p)-7K?*{$aCb9TJW6YQkP_vwhOK9xiEwE{Xz49%_oG&McDB8)^!bY}QJP zJ5~*G%w0L!#6i&TK6>*u*er$wP(~Kar{rwZal@X9{)FiS&R_}AP-;7>C4UdWA(2rD zY;|`h+8HK~`UVl4q6{O~BG`p%$&%^1P@l?8{bit&FHG>Q8y#t~h-$i;?tg=G{uly$ zTm=rz%Ujp_gXsaJQ}C3dgC4o21xfj43dXybA-@mCrJT|6HdtlV_v7+fa<_zj)OM(UxFRT#skP{VPWc|&o$X9Wi- zOWZ$P6aNI^AEA}C3`A_{yXAq1eskSPdT8J4@kSMs#qd$a#@>kBjDP7T7B0P$jAFi3 z-v2}OPT`A322!vZHupVd$DzFQfRqk?UO&pK;2Eyqzp2jVZEbX3h^%|sg%V3UxF33c zK6?y)VyVY9o%kWpCpQqQeDSG+oG);B*F&m>Dy3>?DvN&}uW^mO%OdM+?Wjx`MfS%` z-opt+mp{HcCD$kOXn8`N7Mw2 zk7tC!4a$^`okF`wNDMSJ@ac-s?w^({e7lc#TK8h}g4w^Kg>R~?3D&d6Izk3|^IW`s z*4=re+n8@mr3Oj=s_KpnKGo6Ndt7?4?>Ru&fW>>2^)|ei@W&~XatL^behq}->p9VcM;r(MNk}l`0VR1U2E+Sjc{dQy+Q!GPa z#e9yytf)%6FMVxAbarsU8lLz~Y>omx(cPR)!?jl(hhBsUJ$UR-h(GfpceYjDu6II_ zc5;U}UGwv2ec@8p-FOU5R+J*uP0SNe?^D!RSAFITre^^!D9ZRD> zrfu;o#L4?zEtxFFV|fLn?K3QMq zgiIjh%%yJW<+^|bu49DpMa1=-EBNpa^UO<@Dh(wp&1IHC0c?a|d08vj1{)hs#O4fs zRkUJq=oV~!Cvq;11rUM=UTm!z(gO`7ltrE|p9&3L6i=cxsurt@*jykK7ZXxi+4feL zA@BRX_dqKRJ*;n?=9#i&Hrx(dzo0xazo)ViwEch7y=7D#%N8yg2mt~K5C|4DxVw9B zcX#)NOISDrNN^AC?(Q1g-QC??7QB@s`|h*D9q;dZKVS^#?&?{yrhHSX+J-Og;Mdv6 zg9v`z8C%Pf3CS|s7}+~+89>IH`|#;0APspDJ66@1B9TR_%C#HbyRdE+_9HX!XE&*2tjnpxjcB8=#TeW zKh9W%F^kDYR#mq@V{^&*LK`$YpYVLgq<<&Cr#$_GG~ z@)-n6{5XkE{lTqlv74zzh~f14S^4?f(nDCfDlNU#l#rUQXRFPlzTrnICS&`HYlKO4 zga`5)fChXoK~^@dpPT>d;U_Q8=tuliB&maF30@q*d&C&F;(fmi&%7eNxS}tw3-sl- zAy_qiy?g|EOTuM-8VAi@D|Z6_8?$*^k;>L1>HsLejf^gw;+2C|l{}FHoR8XSO1&0N zK81LI0`$hKL=W#w;D$A=b-o{7Gjjs?o!{Xt!g%(xe-rUXVDwBG$DRoS)@x&$YApSkjKa5*G~WXK7NTev0M;8C`e$ROt3|m4?bL#k&7v}b$ zSxl8khgQ8a2uwnnFhyt!+zIW}&xA(@bd zhN8rdFC(o%oUavCsSi+CBN4f;qE8|c>JuUz@A%y_29yzkL=OAH!aFI?V-+2sV5DF9 zq9EJ2s~{6Iksfh|WOl+*dqaZed;bHNAY%DSO={KayJ)8{bU1H-)Lsjxka7?eFk z_f8fN*dn4H%EjgB{q}=@ZA*pk+^qAj*S;c)-|LWQnW9ngxfRy!jDh%Ov#&Di=7xbG zuP)!fL@aTB%(Q~@OHKTF4N;xHi|@3lM2No+T>OLrb@A!ACs^6Md za8VI>;ushf*@P-F6uuJ+i9&U+Ov0q#qVYyKyM3_yYg6jzdnk{`W4dAgP&7&ggY6Hr zKhAh&y>KKAPGgX{ zP&gCDMVzqJh1w3aE1O?rRQRYVvc}?C3Q)k!h$=9*>itv^#SpqM#W!Vb%jowfqj<60vZjx?46?zpJZ3u->PuD9&{J{;k=<6C>( z$*6M$jewc6PPdaA0XXr@4y8#WdYx2kM@(e%_Cxr)HLycCnszY1Iy|<@1DKBwE*I@L z9CT`RvMMnh@E-Lds=WVHgp=>tbg$`K=s+r{TNKV%z8@vI11<4IfS0z_qpl$4dJ0QG z_y=W(?FB?68|m(>oMLnz(l+9^W7yW~Pag=UsLtgW^f5{(=e%F@;s_{o>VCiqI&~NG zedOo-Bp~q4+VA-D7zUn#UkE$zTJP0sq|A_&Vc`3Y%y!0CwNC`|rfcc$ zzV+nPw%hh_nl;F^9E)gW9%MrMS>{>_C zyytE%-3_8|lq9f1MaVT--@txLJh@3Sm?%VZ-H9|HcK@dKd*^;bi}d{Myy5k=NH`rc zY$VR?&#hZ7aE5C41Nc0>>+cI~Lg9R*Z=n#wpO^5ENdw^?A5oCpvzwpJKj#Y8I}V8W z>VCMVk)>xNXv8!UQxlA;d-p?WHKIa&k|hvhXIesml3OZcqs{ak?SSy~uom0AMnG@- zYg@qAaK{|0w=KAackH4(Wp}-e@orD$WIV8CPgLL1mXWJKg4JkL~XT|wt@SnQE+?1o8Zcdn5SHVUK_-py0?D4J_ zmP)MA#%9iR39eC;zXMcOiJKGJrUPr|GDF16dy6;%{O{d9uwMY?uWI@?F-K|pfwU~e zE`K|)h1;(!9E;y;NW}J5*=4v`@NjCzY}%0|hy#$kuKj3ZPK{eHvG{}erv<)Ahq2q@ zn3N)JC;Wjl{melI6Z(OKEUI zJRSDkuki25xAl3rZyKFGJ9SI-*NhhIdd~VKP3+6-2qKKBO*yWJW!C9TLbiZ9>osG{ z-F~L{5{9&|&vQvq;l2Nrh;}pDl*jYE;SBwI5$BI}x4ucxk~3Q*dT?TQS33sh;^O!f z433&Ly2={5UwN%?+V!OE=5}D=WeG}e3juVk1R5X!?*QB|!2VqQHDIpJHuYKd@Gl2o z^W`m6pkFFhXp&q(TsE=cTk$RVlh3g;G`sv-oOh^lbqlmO8WjlXPkFq@DHcfYs7a}x z%i)ATrLo^=NGv)h$W&TwoIj4e-HOBsUcu@TWp?r-0szQ!!xbij*_}c1RLlV0xhvtg zNBq`flQUp__bwjx&yjKATNtJK+D>_s zYp1qn(+nD7(;1tJg!&yyHYA>c{WGD~(!IbzniIaIk=$|(^zSBJ#cnr!#=(te(BLMq zsAPH!_$v15x-3K_4N-|&D^blppam}gGlTNM-2P1w5d^Xg1ca|l7CiKJI5^lWA*S2y zR*m?St%&n0XQZ{Qq)C4F3b;Gq$v^$mqWpNJ=Y5}okN@f?fm7ibn%?1EDWjDQ*RxK9 z2J^HrU>OE!7c=o{0b3=;+sAKqTQT|yi_Kk14-Im0ZVULA)?_`1yz@+^H&tl4o0YN> zW`bR6XYFTKzT0uw*sqeoO;B5}qm9gv;rq?Y>)wsVIl48+8eoKWg=ki&vP>$ek(RR) z#-1XMdYg4zW(T_N_t&V-yP31@jzs>6P;^1e_mnd6 z;vX8S!hGM8t)vpqDf^JM-aCs*0CK06zW-*nYX(=QcZ9&Df$4=#ib0^~ zWG)l3F6Wqo`9<9+9zoK~GSZF?-p{d{+vKDxRr*qRT=r_H6Q`UNDswZuQ;yf{qcL&* z(8EefmlmsF(1o1!&=5y%?`?ZaIW}f~*X-Q#fkpq(?HnmqEzP{B(O>ZAxA+iWW zZzmqY{)A6*8lOw1LLx!xoz25BBuTw}-C+hq!rAosA;=;RT?kC-xWcOqWvOKwy&i^F zKccc&JWM(4q4w&5bpZ&!bdN+1hf8LFesU`N;mL&bKLR1?#Mc=cW^hEV+u=TNExpUg zbF{z1rtI^iFdti8Gl_Y{0R2;aPj;c8~)AG5Yf;ZDfSz&<%jp`>psezU(~Cu6q26`l0yf4)elC-)Q?8Ui>JyJ zJoj6jrtWITr035`5@IG!M-(IQ9<_DStPDk6Wd%4-*UtavpKm*y5Qb% zOVyblml?M60QzKzu;w@XYDx^0OW-6<|A|0Zwb6mYq01uPa7bWJ7M8Dec1H>%9l;jA z%=Qa??a!feGmaqyi|_Rc5*B}z+Pl}j`mll?coJ26)sKLM&$!SiC^JngF~a&>$ZaRD zD(n+$HKOE}z^FQN*Y|p7c-&xn#pn4vk=d9sORhgDyudSYrN{&b=iq3CZyW1DcSX2V zYhG+1kruy!eCZpMQT^;yJe$$vJbj>&nQL*RwY}0D>Z&nmDm|`!HqA1WU@RSz;@08; z+5+6q!5o0Sd{T%ElPdy9ckcEY^V{%Y4X=)ebBMObj0@&IdY&YTrTbk2=Jty9aMN;X zzI^KA5h1to=srd%$Tfo5F$cGQm&3KHxMNu7G2?1N$k=!%b$Y*bkoQW!w|k#+fT4O6 zG~)gs8C1Jlt}?Ut8Kv?aoxx-OuM5X#O%p#qorJtA3+K4iLj;jIfKEboz^5Uq>0yvV z9ZUN{w!P!P{8Gacdm~_q%w9K34S}~En{P93$8TqU6b~I1pAHFfhf2M9!g5`hU)S($ z5(7*w_@|?ag*h*~MVk7(qd9=3HIV5J>qPq)B{(h3LuB%v1>qvGi~C?mkJ`nkptd!S zX|2ZVR8@-igg~mqL&n;Q!s!vwjZZ&T#wXH#dvJ`?^Q$x-8V83p)&& zga_VvwWt$!kO$hdXLDQ{j!Rf0;7w;Ul)W6m-D#x7{L!e2QnW~8JU%Wayr>w z|A498o$7#S*sWkM0k;AV5cKSgIY!a2kUAy~j3=vCT11bNGMSEet52~%;VkFl^oS-Z zp5B?vJ5_0igmM^jZIJBwxE#R@Bsn$Z;PgaZtRBa5r7G^Vk5tunV02pyu0fhD6Bs4L z&VC@&Boy*PIxdgL|;4<5ogm|8YI3~ z5K#V!eD2LZTEdZbEBW%5-E0#tw`dOv&Qe(CxKY9m&Ko89#u*b&z(U^!i-M2O{!5!J z?BFAYGDP1*(`4Y`%#}YU8N165t7fJ>Cg;o2S!g2X76t_pvV`Huqr9y^%G+eXXb$}*<;EWX68L}_D4E%cfy?(Qx5 z{lit+K?<{E*FNCE>2~x_CMFI+_>KVhtA)``by)o zsp&M2J-%!3rpJdu;#)n0M+)b9IPv;`Plpeg`BoU=J7e;{4Zy#wz`uX-oj_&+X>&e8 zfS&-9|Ic5iDX$>JPSnPcAqnNZUITW!|EkjCO3GMu2GZE9ju#9HA=$PZr@|W}xHq zs;l??J#fXDAMwAB{KYalODNR}ZcA-qM4!L@_czwIt)idKzw28t4xV7m?O{)0JE+#m zbw4+$B(czz6b%5n*Ozv@*Zdoc|Nd+^vabJTI8f^y+MkQf_sTn+nvc)Uw3Syl?8P^S zBZF5VOB>x+=mlBf(=~;^Wx{7?b_+I06>U_>dN47y@!PEl7FuCJ9Bv@$sX&E+{^uyi>iW-G|&~TPC^EyQi-)E zllVoCUtYboy3%xdWEgFCO16jlqbk#jgnLP}fG@?+hR4$^vB*V@t^)ty|@donTe|ces7<-U}H2C@VDE%dYlT&gV9}7`PZg(Zi z6?|jHPunIvH@jartUA!Q*?4F zUoMO#8H&pyB@x=saYqH#%&hA9DSOC{P4_(pli%wpRwr&xn>+$a3W+n^-G@ z<&L9YK6G`FU*MtGCmEfaey8(a4g4Q6*$N?fxF@%Zc+{+uPYRW-1~9T^9Hm@>1C@ZL zg?2IywR%}PJ+b=-m&@sJfRng|0j1>WLtAJ4`koxa2+=V>&jK;A$1_)NWcxsXaVJb305CyFM+R^}{i_YFN=6*L`fPmmX<# z&+4IfHv9hW4}<^PwnKY+$-IuFoOb5i8an{mG2aEJI0Llnr*X-7GgSjxy) zM@Av4Vr4p*cfjO1&=mQ71 zfM(u@ zJ}^}n9+2AbqVvSlugeF4x6P0#urHDtK}hF=SdGa}8&CSOhbeFpBSIl4z!FTmv7EJ7RB~t+nl*t85M83tOpCRLhaxHe%iNn6Pqe@43I@E@>*t zZs^%`Sthr4{5rJJ05FTKJX*k=-ap)5?h$xF2S*XXa|5+yP(bpa!b*--z~IV0+J{+O zt!V?{V5h1QamRvAEXQ-C5-)3AH?GEyT4j3xB(-}#NO`8ac%Krd6FID(&()c^9Sdn% z&xoZO9!f)-q&yo{^jhn*WB>p}vDhb&j^o2~)_!|yiE|A|*Po~E0=JlE*N7{W0Hs&c zJuG(l**Uppe=i{e`wxlnuLk2hJrK~P?lWn4ndA)*KKI#VLdt7b?dlRkj)gsi`dn0$3bf%+aS4V((T&4acYQuH{2`bujoz-G4^GMA8 zv1@5eqqypR^?j~|(*ff1#6po2R`8?B(tRf>#lz``C2`q`ji6PyKPcjW4L-K%dIJfY z)HgaYu&5^zrWa#uF?8Zt`m@G}a-`@|S{gwieLZGhuFFFz<>L~$ zNFajE8$vB&9Cg~IT!7!hRbNOvFykVbmBn$`R;w0=c3c)ltY-S{T}7i#&kO(#LhoMo+CBhi=9nLuD>YGN z<*^R0GeH{QTAxbrhrz&zfXplm8W7p^y%;6bSXHzrl&-GXRkY7k|Fl0juWH@G8s9Yi z1rjCivJr2Tnbkw3mCjW_+dj%d^(=mw$Oki)CIdrL>8Ei((O%*SGV;I`u8 zBUrNIbvJ#!!TP_I zZ{=?|Jo_o&c=u^YzPi9x7#NNv`usoZ@|P%_emvbzXK{+JaWyuSHY>JrGm;2v7acHr z9a9rpY!THwQ}c!Yh2R7ny{(b2PI+Y|ADKoaW4fF)SfWlc)#T4@%~>zIwQTfwIFoAh zOjqB`8k(pgfTHtO+&2iCoos1k?9L$FuS3}HfwW4{C1gsa?hpJR8uwLT7U>_l34Tcg z`D!)sf|IcUziFDl7|q1>JK=dr*Se4gFRX{X6jZYTOD)%tFd8xY$#THmWq9HxySiuk zT*tW6u{5a+MeS*wNA*+qG<$7>!_U(nd|{AAMn>GRaxbs~oFU1}%j4lLnK2gC<{JL7V#c=%9}W7y_3B- z97%QB4VXif*NpUVP%5CgR7+(Fereg-deOi^wV112jnp+0z!_*R)P!cR8n=a?$n``Vxh z4lYq?u@D2{%~(j2)e*FYljLJKE2la=e46u{{bH12aSZfQC>77_Y$YIQqj)$zx^%k` zpGjJWcg7#FHBML#9E_mh#fooLuvct>{;N`Di#6&XeZ-WMritqTLjDE?goy zMR>)m#%Dtlv*R(!ko5a|;yv}_JmkEGQo45GRygjvg5C0;HHtpsM3Fw*)3Q+eqz zTa~+j23r+h_2lC$_!l#rn+%@k{C&~;-`dq98Pd|n%}_O zmr@Vv`p;Ywx~3G}s%bd^CSmuT5TEZE%$94}T{w95!7$d-`S?aRs(|*QsMGmn(IxyXy2s!n z?vRrLCch%orZu!*jGr|(`dXk739|Js0vzkjUNLM##gA&c`#zc8i`VJKdN6US2Gkyp zGt*ki=G>{xv8R!9j*qy7OS#XFj?wH%Ps|YRUlvxUa&*Vu z@kiK=G`Xt(BRTr-Kif=xhd&KBK~j$-Zc2dO{ zhZxK%FYEC~#-_5zw#(uJ;g%A$_7aRR3QcbmJ8RnGvL_RHXF>{jrUpixKc5Zi3Qo|mn7w?i0$5nKtFje8AhVbuBNGZfWF3>Wj2U= zR>5pcZkieNkyYw!O1ckCJ)d_?IOwVR*0?E~QpO2*AErb{CBuvh_8jD25K6|KGnv`v z+et$oj7yNPom39A(9%ha%h+Re{nx$zkJN*wW#KA1-_8a0(uhq2%|js%wFCwrp3F3#pm89&(&Jdq zeU-%1O3o#$Ra z_i*-zd^!g-?rn-~;gXGtcx>PR<~}XuW&%$)kU;Bhl{Z_p3X-VyD{BORw?PLj9Q7~H zUT!t1zV&G}8hwGW@+fEbpfVYv-rQdQs@0k+k5!VcqS5fD*+P88vF;|GGY%uE)WV)O z5tJLHO+mF%uDXUpLA~6+DqKaZD~XcYg-%><5~-ZDl#~bdS`>f84zD0P)bJ@N3S`1| zrZ6LGM4b7?posx#w%P1*4?0q#XA?f+J$>^vq1;jrT=usioJeLDu)^wZUc$fNp)`I& ztN|n6NDVm3!NZ>X&}JgQs_j{pX^)JFbfZa@);{;@gRm1W?97WZ$6vLFxPktzebNh! zQF=c?vh;AX6xvM4jaXj3a^w+NGdTSjW6#m!lVF;Ncak&f{+JAg+%>?T+RKD5fxI z?R4KBI>R6Ne$rpD3_pD$82OT#8C`~?#p+x@BMx^>waWQN{@`8OE4g5Z&Kydp23SF1 z%86FBIX@{}A*p_H^XOLfD)Q0|$?o%CKvcgFC3UZ2?d(HBRCOK26WbHfTlGSjrw4`R ziGV#fOaki;Y3|3{_3)iyOGe0&$Q(LH2u zCvP&T(CB4yDzg#!h?;(}j+S{Z~qg7wme%Sw4$>#3>-U}Vdt;KKw-XQBOlz({} zSvC=LK6|71ow#hI`}iXq;1gQFqazuJ zA7mdmR5tUF^#*0KicrQg&s45>SkBxbx}}j!O6<#UW5d&2+M~?P{`2;K zBXaWlX%|~N$xeqw*emS<*yOP5g6184W*VwtIQOZR425}%pJc(Yj?>r1xNc!G!R2>FI52ENmD+H zPH8FaLOh`xjZ7OOR+zl$$FQd*ZoBKD})S>i#!RIQ%cKQLf5>xZ{$2{Bs zpKhncAI2mHT562Uu0=a1?&^-~d^Os0%Gpu~7M@E<^jbQJpSBNs&P*DsU8!b_vYZMG zd?v7N_HOR3`kk7B?4#M~yReB^mam32J<)06O6m@qfrno!%{NH)uez?SReP>+HiDg- zWy9o7QU4jg>AXb>zl@npV!V2P>Q@Ocr+k^#7r&f`~0OJmOUMCC#7yW zW4geX&F|1kXg;T~F$AOZH%oFyEk)0(qJ%N&my6P;?%szL2-186}%Ozr#Q!Su@us`DQ7I=Jb=$CMw za}Kvv8w`NYm`8BNp;82HJAkapF`Xrfo!rY5!mI`-PD&-u@>Ftpce)S8N{1&AA=Q;a zxwHL*4&Jub#3IuQIuW>cw@S+2K=4YLoPX~c6V3KzL<`3la>zReq>Q7UT8tO%ze}0} zn){rI4Y9g2tiGoZ8-R}sHbtv&rDZqB!>xpT zt*2WcAHjo3>Zk-p53blzRyV5M9-MZ*9$Dl za~cG1Q{5^pr4}8ld-b5xoRgZhcb_Y`H}U|Nq+|~M4(LYaQ1Iv;35J=cj^RPpo`%Bf zOJS#!9!@421(c+AQCAbu3Yx%((3r0!My!mInWVdoJMn22qTl%etMWb7PqXA&0LYd~ zoTJ1W9M&OA#8qqK!pMI(AS4V7a1IM0n6VjuG>9*FeEs^s*^jt^GGBYa+_#Et?5@$r zmV8Yd*!m0VfI!I*9MQ#r2$S2ZQW7PC_v+l(9f~|hVg@LOB6U2CgdFad1l_(fD6Yfy zX2+|2|M1zPzqEwzLCv4jEP1wP*!F~n2#ol{>L4`A>znCq1ao$}$#UeJ= zSu`C0QJxd2*<771c%sNmSv!>!4De62Huhg$On-{kVhck$|GHfO*<6_|TlzB*GoFOu zr{O&nLwj3aiPbm*sm0ww{ryfWu8RL#v=uIApGa(o=4;zQgbPh4z`4NV5b|U9HC(8* zxA;C&A)}S0S3=k%hfhd^P_??X=1=DW6!T?~eq0mTX*I>-BpLgwz(4 z9hB>=TC!9dD_yx`Fs99YX_bv;JUkh7Jm! zbTUyboF3N|q1()9klg5mQtmnri7~2Th5E)rAWIF6r$8-WF?X`g0)5&%F+P8WC9!lF z8_dzHRJSciZug}(_ggAEdlg@AJp7Ln4hj>lRwe+Gs&x^`p`L% z@uslFa6WdOR(7K%#r`DrVrmStG{@fRrlz06*`ke`&sqJ2T(O0TSmuM4iho$K*TmpX ziL|Ue81H2$e*HlRmMdWHi?YKDE<7dc>kpk#yx+ZyE zeH{*E8f`b`NGVWM%m#aR-LxW4{Uoky-S~}Dso6|~z4U^;%9XRj?bD=3_xld|z3oXTldbT# z>O4H{h9}U0KTTWGv@?mh&+7UsvRIN?|ANN?tdp(hP8%k-%DFhojGB zSxI7-7>HV8B!2BU;#W>_?@(w}!jr9@oC!u{S8)&D0InvROxz7$t2`{Od7CV*J}dNC zRQi*XX@|DW5dfQS%36^S-(xHG`*6ZIV)c_k90!9c!UH; zeU1g$Ld8<=&GSnYBm|Px6Nh;GNIW-|1|A+?fit=8C7gzoEU3FT*4Rp--44{L7{`kS z<;&Q=>A7KdV}=Aal`m5&I^vi%wgi zmcC~D;AY5pcKJ_P#UEt_*GS#%GteLjRdu5Ya&rz~6h!DC!v36}Hi~Xv%?z2@i?weE zC}~nYzHd_#t)KneX?2j+hCI|)6c&|6qt$+vtZ~nc#0;o1-qJ z!461pP%tosysZ!z-@@9$|D@iV5xK1V0~X<)#iym2K%FXW50v}W>1E;z66UR@6N>?DX-Ke$h$>oI$q9*xQ{&?dWZj+ke+~_y13$}`m5e!; zs+)xqH3J!0JA{#2D)mD-&hsG!$a1^u0^qEgxiH|z=4F)(DObaZY~G3S&HmjLfpDjZ zPZ#&*{sFJ4{gEp8g_6^0w%1Iof-`L$a%~ojL;~E#!VT$%gS#7vh7IN1+57l~_-x>Hv#t4+m1rV8=f8t~am8Xke!bgQFxt6M_ zzgQsBMS}ZEjbGndp}pbDnZov{_a)n4@5%r^s*$Tmt3Loz1hUPCilVImt~LQi>B+n8#wNBCqEh2G}{-muh2nWV0P2ukws|L z9K|2DL>^y`lDeo{Bb1l#-ILSb4<~&E?W~&}uOp<^6j#Q_Ci60{oxLC+smUxZ8Qm}nfrEWJj_kT5(cq+C zMqpuniRW5?Q;8Ea5O~!a0lX6lI4}=;I1hrqf zqI?xO^;5Q9L-T}Np|34trGq1vbSf_P=B+Y+spH|AZya~dqz*X(pN=h}{P!Oie_;pA zb_#cyZwXfNnh1qxa~v%xzF3jawSoWY*mupy3C~Q;C3>OHKc52J>lB?Mo&|rN{OoJ8 zJ^A(R>Z>`o5#Tj6XVAGHH|hK_PJ<3b^|=BsHyf>bt%>y5Q7DX=oQrL~G7iU(0?xop z7-ZBXsPD)95Rpc$(iZ9#@Nr{3^g%o&2Q%=WJ>&nP2_yJln@8>17ywei9Jtu5`p8!D`r&Q_EGO|Ex*@4R{bH2U5jz*TU}NUJYcnvVzG z`i$@uHyswRf(>1_f-h>eA?z&?zS-Vlz|1T+(NS>U*GkESX2FGHJg}i$rl+iE1QCls zbIZI~E^F&WIaT8umoj@D8U`40);WLiu2*1W&}NZq8Kx_Mi}R9zK|tM;fxG;}WEEdD zKCiqP(ZLzjZU3tOEz0~QP43|4_&E`>f)|`NSQFdge{Te^7kIfe^*7w~qM)n}f;-ky z>OWKe^|t>}ZazHlkrW=HYhywK#&U!2&0cJB|Nr-L3*VHd%oKzmUe8=9=tsvOm3+N$ zKZ>y9u3=uWIGR9unO*O+y&>L<8h3?F-Hb@d$F-&keAC@r1qHSDz9<-{er;Z0pXMJn z21X|fDSGaDsEmXQOt&xZhv-GZDB~vbY_O4s8G9Y`9;c-KMM(zt$I#N@Lx^#5jvwQQ zou{`Qer$HS6QsV-LudN4m+oJR1NW{s@+b%#+=IjAL@mNBIn|R(Dfidlk!d zHN;vWb6G2~&;&pVZohZn&nijA$VP>KRJ4Hnw$ME7QjhD>2vSovlu|L?c)L_l&pl$H zHTE*IeKmFYAR`|r8>HEj&`~n1rNa)|P|T%QTJ+3=LuZ(sdvET9+>@6VOg#FLFApw$ zIrc1yg9bu5Ols+E)!8Ze_u7Z2j;fiJfVL9eHBwSz*6SKq|Jr^5@G1vB2AcYP#GMS( z0=Yqp@UG?H?Y!NTa&%O$efX|TX3LEGG5v)4yOMEUDx{^dedhGe=E06g<3W$k0Gi~a zRQXehT3T^Ials@bUh?R6P5JrD9n0S67~x)qQuY!*NfWIhBd>ECtD3WN8ZB3LVm_L> z3+0sreU~lTS1hvOW*`+OXBKL>@nyh42paXp+;G(B76U9O=PnYV@JTXvfIs1+ZeAw; z`04W4``G7O^ZS)48Ta@ngGx1q65S;RPPv((#Jr5KP*bVq);&h)_)PPwU;YN_DmZx( zJ0!cImp|^o$HajaajG1g>Dj;3c8e$SFdF#{1%|+>DEEr_L|EbRyyV)36TC(ctIJ{L z9}V^FQt`@i>Xm2@*~fP7mrK6RUeZ2~^;f+o{XcsYt{WQo!+tVT;1h{TNS@N=nn%lL zNNtY9Sp7gyV}MigqPnrcLnia9EaP~;7X~-C ztLYh&JLyb-p#ii9X#r$i_jspJc=0I}MsigGC?jgI{#)^f(Od3_Foz?nx9Ea@oxFv{#oV9 z7KMK(1piP=kdc%xWy5e;VM-xy4! z6^-TNJ?`ZqoKlNFkI}wpvxM77A^MhmBAmpo1yZr6!Dy%}fGyNy?0P++68F2qBjCFj zlSF2=CS&Gkgt^8PIJ+n$B4*K_q=xfMHNCwrx?~y)SjpgHlq>Jt@JbA2c5N;&mjgtm z%KJzb*QiC7i!;q^p>8uBXV6omV&<_mo;L$x4a>%T2Y;+y0azXOGU|#nM-nEQQl%uv z;qCdWW+%Z;-tWgtTQH^yFMarx?;amvv*UL+b6#&Zvq8BSo2=gpcLG58!uROX^0OP{L{1F|>`8G!nLZHcV={ zP=VQ}&iuLe(9?`o(%fpg=Jg%0-KFb~g#W+rPZG1YJzDXyOFvdKVzF)(Yf2o8e??pV zv~se!nVf}!ulivA_V1nu<}cduk0H*qc>Q7BvwvC$?=RH9BZ`|rvC%J#4`-YQhftMQoZc`Go!FJeJmRd{d=&rVCySx%a@gg6fF)Iw?R)dM*P1iW~E+6tuh zg24E2;Ny0%Er*d$#C4ReVe85p))(O%-QgSXj*j!a8sNm1hR@5bdq}5X2q>2N|Fn0VQB7^zT0jpLP%(l?N08nW0)%d%gLIH8N-x19 zfgs(09Het-(ouR5kPr|-ARgKU0-;1==o$n<5>Ox@{UvXl_wK!tfA{a*zxEh&?6T%w zYt8w6vuwaP1Kkz=-v|HV8UJUhMtT-)YcW%creSb6Tu|}RAOBnO`fqOWX%=2u9I_y& zNALKepaKHUbt)c+-IrWoplhc%RcPYjwnwG(c9YXA!BjkO=a7B<1bs0tj6oH%_&5C& zi=rAh%KFE&*CpM0Y|2T<&kZg>cNFK&-d<6;2%U5>Ei@xq^C%1*6a7Y5O3Db|mx9d@ zx7Y>38S(^!yD-_!FvqnY?b7M8r&-KkEiKBpqZ5}Bd5UFIA08ugkykEN*40fM9_&#@ zb@?g8h@z9i{3=TNb7;M>{p^c<&hW2&-eJG*p&FiU%q-$uiJx1yVxJ>T0aVgc^dbo3 z#Dzc3c+{V5VM-4NV~B>$U^#y(me)P9xvpFNw$4`Z1P&BFhp``>o-89(Os%Whe4|!y zNK%8s%AS3_$m6Pp!7hnONQ@!8#&lzn~eTZD<|^HOrm~DPX<*(czr+hb+C(>+f8dS6ekTiE#?_v6HIm4IA!vyu;ZlL zWsxq&2&xZGtjkvux>(a!uQz&D>i7#$r^a&ZGpvVRo$Q;jCscqALF=g&Ht7yDwYN;% zKQC(H>KUblwBZq9Z^W_IolWi{wcuF%`LuwVZ3%wq= zq{FwDh*Lh>E4kY#UTslLPvINi`8?Di{y&8^!IR;p*ld;UMFnwTMZ2KxHoGccvk2R` zh|P}ok>pzkv1eS%MRnS;p7#+Z^j$1N6-<;4!;9*0rsdNdfD39XXLTeTQc!W`2H|n5 zO$U#a>qurUf%ZE2w#sCKkFN(jWdbv=h+{SH+n^OhbND3#5_e|ve~Y=RyDVbEq72n3 z_Tv@>@1c^hiA$ad<{~}BKko)bM;H`ctS-)1>)Qz1YvlTroP?>BH`*ywn@O{%umgHW zUtLRz6y+pX;{kMdpI1RN=C)H_v|}(1*X*MTfLAOy{YAF*yYTV7$Gs?zthEmQYEvkl z)2;Nkw%yYcwKrT)sD6K})a{hI6m}vuIB*!3O!-9fs`chMQ_h-4Jb^ih`9c?XH7v`f z7f0je0jvd8<~7hfC^=CF=yS)b(R=8%hWOUa;KZ*+POE%2ml2aEFB`YF$k`zOl>(;J zNNzs0En&PJh3IxtH7`*l`@ekYbtf~0QSgS%R&1P|VrYpFTaTXJCYr`b|={JC=(j`$lQ9u@m^*)grolOH-<+a9zRQ3f(Y z>LskVweNantsqo22BhNrM#_f*$1us!c?yEJ=5jcSyGNIl&ynEXfLA^y($$szzX?rk zsCC3>FNZ=NWw6~Gc+Ldoa?A2-V@%aflE}SU*KKJ5M1V6#r0%brBJ7LAMU`L}hmnbOm}MpI+FR-$9Wo_`r)t3V zTQwvp4FokVLZx*7@x;`4smm9BsTcZLb8N$w{6(=KU46QI!+do(@I!F>7t;d2A(n|I z_bPvfeNJs_D*`szHj%GgScDKuxK*ueUdjx$QE?Zibye_toH7$^xw=|o>mNS*v<#R< z+(|K6Ru4Vfs@@-34(ZhWR5X&H8%3GV5Z@gX4$U4Gx5{8Aeo^NG1M_gnkNHQrQr^fP zNJ0a#2KL#FZMWCMWG+@BpKWB^)B?nWA1Vj@yB()FV@y&P%Rq)Q*!@zuCgvj@ zpMbmhX=L=K?c~Pn9O8#mf%UXTxx8!eL-ZM3Q5ispGV)#@T#NH*^PSo-<+k^{(AnTt zNNA|tVd6>**aS01;(!(zIoYpLUziPmOcy}HOWR_R+KNivP1NYGJRapw?5J>-7aB!5 zt1X|^ndBb&qEPtd^F|4ymD%rikKkRXVNyN~hpp9big_j3{Dn=DPy*brxI;dl(NhqL z-<3K;a-h_QD3Lz0Lk7OWw<_Yuqkwa9Za&@UsHO~pXNd68Ik^VgS%toJYi)+ z`0x~K)Y^NNoln+kMqga+niIYx8mJ;?Coj`(kQo_j(Wd(51Yb5Y#ZGHX-9feONAp!Y zPlbEi2FYvk@nWV=sD7=GPlVBnI)hctL@AzmYFc#z?ttfkgwM#=IF})oUM_V=WqT>v zzC==hbFCg2v*=2=dd5y+B4cYOroa-MQ1rF9>vEn)xo&SCK|`K~a&|hLec9yy zsbVt6q8e!rl>qND4(Vsh;A-!a=Rn7DW2kw3dB~rJ3WMy4zEb;^k2~HscCC{}Yz+e_ zQNpK~bMKF9HzM|iX4;z;({I)@3xnS_Y{0>){@l+39FE2mG>h-9L0MgA}dNzbT7?# z0%K>5{B^3A&;^xiiQtiX#RZ=N$6@eTY4_dmITC26;LNv>1ulaKngAuL8)JBHVUSp#m*`FkvMuM-#YV_}2 zdv~4?N}~irKHX|`n1Yl)q=r5DgvfLCx;lJu=(#0v$UOS*n*P6oC&0mVw{`6Fhf4H; zXfCor$pSwt8gKwM4p}G(wOp@HK&%)Zm1)k-D<{n4&5UsTx)e^cn8TKeA`cUFvI1ww z21##!{KEuMU4cu%%VJrNQhga;1`b57q3yO$iGB#=Th6p`!hve0d?1#HAd4gr87Ggs z;~ip9FsL#&H6Gq2-8pgcG-D+`4 zW5Xe&u#qI`ck+VZoC0I2zs|`%0qhU2m*&?e#DII^?Jt(ldNZ&~*Di8GtD5U>5uxlW zF~gz}DZ<%o+ize74!bVx^C7Fq4Zab6H20I!YI4VvlMmB+5?+!cc(oGw{b?iXv|nIQ zDs(yj^b*_;qGdpsm0u>En_ZziE9KQ)LOxoG_9LP6wyUPL#t#n8X>^Tr)^<3g)XVbk zqCW!Yf!%y7So>ZAP;-{Y4v=3NV;dI|7+#U;is5lo0ZoE>$F2e3JULqb;*=4eGJ9Quz6~EYRo2vo3_b~azynZIf zsscoqS%dYy7A*$o8mUJYS_#Zc!nnX8q3M6s4OqhC)PBq(> z+ZSHC;}byoiDXT9wt%!92OOIpv@47pIZ2^!L+5Wjt) zFLt1cnX!A$3UoEe;zI?()W6ZJ|K7pj>F2iUqFSKl6 zySuw}ISczC<^&(KJNufQ96$3{YnBK;AI bJYu^3@;r<0W3u!K>Y=ZF?{3{~=cxYyA9_T{ literal 63668 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 Date: Fri, 3 Feb 2017 16:07:55 +0100 Subject: [PATCH 55/78] Unify reloading (#3439) * Introduce Runner interface and wrappers * Remove duplicated code in filebeat and metricbeat --- filebeat/crawler/crawler.go | 25 +-- filebeat/prospector/factory.go | 37 ++++ filebeat/prospector/prospector.go | 28 ++- filebeat/prospector/registry.go | 46 ----- filebeat/prospector/reloader.go | 177 ----------------- filebeat/tests/system/test_reload.py | 4 +- .../mb/module => libbeat/cfgfile}/registry.go | 16 +- libbeat/cfgfile/reload.go | 179 +++++++++++++++++- metricbeat/beater/metricbeat.go | 9 +- metricbeat/mb/module/factory.go | 30 +++ metricbeat/mb/module/reload.go | 174 ----------------- metricbeat/mb/module/runner.go | 7 + metricbeat/tests/system/test_reload.py | 4 +- 13 files changed, 300 insertions(+), 436 deletions(-) create mode 100644 filebeat/prospector/factory.go delete mode 100644 filebeat/prospector/registry.go delete mode 100644 filebeat/prospector/reloader.go rename {metricbeat/mb/module => libbeat/cfgfile}/registry.go (59%) create mode 100644 metricbeat/mb/module/factory.go delete mode 100644 metricbeat/mb/module/reload.go diff --git a/filebeat/crawler/crawler.go b/filebeat/crawler/crawler.go index d5a388214e6..8cc710e7d59 100644 --- a/filebeat/crawler/crawler.go +++ b/filebeat/crawler/crawler.go @@ -7,6 +7,7 @@ import ( "github.com/elastic/beats/filebeat/input/file" "github.com/elastic/beats/filebeat/prospector" "github.com/elastic/beats/filebeat/registrar" + "github.com/elastic/beats/libbeat/cfgfile" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" ) @@ -16,7 +17,7 @@ type Crawler struct { prospectorConfigs []*common.Config out prospector.Outlet wg sync.WaitGroup - reloader *prospector.ProspectorReloader + reloader *cfgfile.Reloader once bool } @@ -45,9 +46,10 @@ func (c *Crawler) Start(r *registrar.Registrar, reloaderConfig *common.Config) e if reloaderConfig.Enabled() { logp.Warn("EXPERIMENTAL feature dynamic configuration reloading is enabled.") - c.reloader = prospector.NewProspectorReloader(reloaderConfig, c.out, r) + c.reloader = cfgfile.NewReloader(reloaderConfig) + factory := prospector.NewFactory(c.out, r) go func() { - c.reloader.Run() + c.reloader.Run(factory) }() } @@ -66,25 +68,18 @@ func (c *Crawler) startProspector(config *common.Config, states []file.State) er } p.Once = c.once - if _, ok := c.prospectors[p.ID]; ok { - return fmt.Errorf("Prospector with same ID already exists: %v", p.ID) + if _, ok := c.prospectors[p.ID()]; ok { + return fmt.Errorf("Prospector with same ID already exists: %v", p.ID()) } err = p.LoadStates(states) if err != nil { - return fmt.Errorf("error loading states for propsector %v: %v", p.ID, err) + return fmt.Errorf("error loading states for propsector %v: %v", p.ID(), err) } - c.prospectors[p.ID] = p - c.wg.Add(1) + c.prospectors[p.ID()] = p - go func() { - logp.Debug("crawler", "Starting prospector: %v", p.ID) - defer logp.Debug("crawler", "Prospector stopped: %v", p.ID) - - defer c.wg.Done() - p.Run() - }() + p.Start() return nil } diff --git a/filebeat/prospector/factory.go b/filebeat/prospector/factory.go new file mode 100644 index 00000000000..e04e6213fd6 --- /dev/null +++ b/filebeat/prospector/factory.go @@ -0,0 +1,37 @@ +package prospector + +import ( + "github.com/elastic/beats/filebeat/registrar" + "github.com/elastic/beats/libbeat/cfgfile" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type Factory struct { + outlet Outlet + registrar *registrar.Registrar +} + +func NewFactory(outlet Outlet, registrar *registrar.Registrar) *Factory { + return &Factory{ + outlet: outlet, + registrar: registrar, + } +} + +func (r *Factory) Create(c *common.Config) (cfgfile.Runner, error) { + + p, err := NewProspector(c, r.outlet) + if err != nil { + logp.Err("Error creating prospector: %s", err) + return nil, err + } + + err = p.LoadStates(r.registrar.GetStates()) + if err != nil { + logp.Err("Error loading states for prospector %v: %v", p.ID(), err) + return nil, err + } + + return p, nil +} diff --git a/filebeat/prospector/prospector.go b/filebeat/prospector/prospector.go index 3ce402298af..31f48684596 100644 --- a/filebeat/prospector/prospector.go +++ b/filebeat/prospector/prospector.go @@ -34,7 +34,7 @@ type Prospector struct { states *file.States wg sync.WaitGroup channelWg sync.WaitGroup // Separate waitgroup for channels as not stopped on completion - ID uint64 + id uint64 Once bool } @@ -67,7 +67,7 @@ func NewProspector(cfg *common.Config, outlet Outlet) (*Prospector, error) { var h map[string]interface{} cfg.Unpack(&h) - prospector.ID, err = hashstructure.Hash(h, nil) + prospector.id, err = hashstructure.Hash(h, nil) if err != nil { return nil, err } @@ -111,10 +111,8 @@ func (p *Prospector) LoadStates(states []file.State) error { return nil } -// Starts scanning through all the file paths and fetch the related files. Start a harvester for each file -func (p *Prospector) Run() { - - logp.Info("Starting prospector of type: %v; id: %v ", p.config.InputType, p.ID) +func (p *Prospector) Start() { + logp.Info("Starting prospector of type: %v; id: %v ", p.config.InputType, p.ID()) if p.Once { // If only run once, waiting for completion of prospector / harvesters @@ -123,7 +121,16 @@ func (p *Prospector) Run() { // Add waitgroup to make sure prospectors finished p.wg.Add(1) - defer p.wg.Done() + + go func() { + defer p.wg.Done() + p.Run() + }() + +} + +// Starts scanning through all the file paths and fetch the related files. Start a harvester for each file +func (p *Prospector) Run() { // Open channel to receive events from harvester and forward them to spooler // Here potential filtering can happen @@ -164,6 +171,11 @@ func (p *Prospector) Run() { } } +// ID returns prospector identifier +func (p *Prospector) ID() uint64 { + return p.id +} + // updateState updates the prospector state and forwards the event to the spooler // All state updates done by the prospector itself are synchronous to make sure not states are overwritten func (p *Prospector) updateState(event *input.Event) error { @@ -184,7 +196,7 @@ func (p *Prospector) updateState(event *input.Event) error { } func (p *Prospector) Stop() { - logp.Info("Stopping Prospector: %v", p.ID) + logp.Info("Stopping Prospector: %v", p.ID()) close(p.done) p.channelWg.Wait() p.wg.Wait() diff --git a/filebeat/prospector/registry.go b/filebeat/prospector/registry.go deleted file mode 100644 index 71ca0c82253..00000000000 --- a/filebeat/prospector/registry.go +++ /dev/null @@ -1,46 +0,0 @@ -package prospector - -import "sync" - -type registry struct { - sync.Mutex - List map[uint64]*Prospector -} - -func newRegistry() *registry { - return ®istry{ - List: map[uint64]*Prospector{}, - } -} - -func (r *registry) Add(hash uint64, m *Prospector) { - r.Lock() - defer r.Unlock() - r.List[hash] = m -} - -func (r *registry) Remove(hash uint64) { - r.Lock() - defer r.Unlock() - delete(r.List, hash) -} - -func (r *registry) Has(hash uint64) bool { - r.Lock() - defer r.Unlock() - - _, ok := r.List[hash] - return ok -} - -func (r *registry) CopyList() map[uint64]*Prospector { - r.Lock() - defer r.Unlock() - - // Create a copy of the list - list := map[uint64]*Prospector{} - for k, v := range r.List { - list[k] = v - } - return list -} diff --git a/filebeat/prospector/reloader.go b/filebeat/prospector/reloader.go deleted file mode 100644 index f8f21da39d7..00000000000 --- a/filebeat/prospector/reloader.go +++ /dev/null @@ -1,177 +0,0 @@ -package prospector - -import ( - "expvar" - "path/filepath" - "sync" - "time" - - "github.com/elastic/beats/filebeat/registrar" - "github.com/elastic/beats/libbeat/cfgfile" - "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/logp" - "github.com/elastic/beats/libbeat/paths" -) - -var ( - debugr = logp.MakeDebug("filebeat.reloader") - configReloads = expvar.NewInt("filebeat.config.reloads") - prospectorStarts = expvar.NewInt("filebeat.config.prospector.dyamic.starts") - prospectorStops = expvar.NewInt("filebeat.config.prospector.dyamic.stops") - prospectorRunning = expvar.NewInt("filebeat.config.prospector.dyamic.running") -) - -type ProspectorReloader struct { - registry *registry - config cfgfile.ReloadConfig - outlet Outlet - done chan struct{} - wg sync.WaitGroup - registrar *registrar.Registrar -} - -func NewProspectorReloader(cfg *common.Config, outlet Outlet, registrar *registrar.Registrar) *ProspectorReloader { - - config := cfgfile.DefaultReloadConfig - cfg.Unpack(&config) - - return &ProspectorReloader{ - registry: newRegistry(), - config: config, - outlet: outlet, - done: make(chan struct{}), - registrar: registrar, - } -} - -func (r *ProspectorReloader) Run() { - - logp.Info("Prospector reloader started") - - r.wg.Add(1) - defer r.wg.Done() - - // Stop all running prospectors when method finishes - defer r.stopProspectors(r.registry.CopyList()) - - path := r.config.Path - if !filepath.IsAbs(path) { - path = paths.Resolve(paths.Config, path) - } - - gw := cfgfile.NewGlobWatcher(path) - - for { - select { - case <-r.done: - logp.Info("Dynamic config reloader stopped") - return - case <-time.After(r.config.Period): - - debugr("Scan for new config files") - - files, updated, err := gw.Scan() - if err != nil { - // In most cases of error, updated == false, so will continue - // to next iteration below - logp.Err("Error fetching new config files: %v", err) - } - - // no file changes - if !updated { - continue - } - - configReloads.Add(1) - - // Load all config objects - configs := []*common.Config{} - for _, file := range files { - c, err := cfgfile.LoadList(file) - if err != nil { - logp.Err("Error loading config: %s", err) - continue - } - - configs = append(configs, c...) - } - - debugr("Number of prospectors configs created: %v", len(configs)) - - var startList []*Prospector - stopList := r.registry.CopyList() - - for _, c := range configs { - - // Only add prospectors to startlist which are enabled - if !c.Enabled() { - continue - } - - p, err := NewProspector(c, r.outlet) - if err != nil { - logp.Err("Error creating prospector: %s", err) - continue - } - - debugr("Remove prospector from stoplist: %v", p.ID) - delete(stopList, p.ID) - - // As prospector already exist, it must be removed from the stop list and not started - if !r.registry.Has(p.ID) { - debugr("Add prospector to startlist: %v", p.ID) - startList = append(startList, p) - continue - } - } - - r.stopProspectors(stopList) - r.startProspectors(startList) - } - } -} - -func (r *ProspectorReloader) startProspectors(prospectors []*Prospector) { - for _, p := range prospectors { - err := p.LoadStates(r.registrar.GetStates()) - if err != nil { - logp.Err("Error loading states for prospector %v: %v", p.ID, err) - continue - } - r.registry.Add(p.ID, p) - go func(pr *Prospector) { - prospectorStarts.Add(1) - prospectorRunning.Add(1) - defer func() { - r.registry.Remove(pr.ID) - logp.Info("Prospector stopped: %v", pr.ID) - }() - pr.Run() - }(p) - } - -} - -func (r *ProspectorReloader) stopProspectors(prospectors map[uint64]*Prospector) { - wg := sync.WaitGroup{} - for _, p := range prospectors { - wg.Add(1) - go func(pr *Prospector) { - defer wg.Done() - logp.Debug("reload", "stopping prospector: %v", pr.ID) - pr.Stop() - prospectorStops.Add(1) - prospectorRunning.Add(-1) - }(p) - } - wg.Wait() -} - -func (r *ProspectorReloader) Stop() { - close(r.done) - // Wait until reloading finished - r.wg.Wait() - - // Stop all prospectors - r.stopProspectors(r.registry.CopyList()) -} diff --git a/filebeat/tests/system/test_reload.py b/filebeat/tests/system/test_reload.py index 5df8131c3a4..9e6717f3257 100644 --- a/filebeat/tests/system/test_reload.py +++ b/filebeat/tests/system/test_reload.py @@ -71,7 +71,7 @@ def test_start_stop(self): # Wait until prospector is stopped self.wait_until( - lambda: self.log_contains("Prospector stopped:"), + lambda: self.log_contains("Runner stopped:"), max_timeout=15) with open(logfile, 'a') as f: @@ -117,7 +117,7 @@ def test_start_stop_replace(self): # Wait until prospector is stopped self.wait_until( - lambda: self.log_contains("Prospector stopped:"), + lambda: self.log_contains("Runner stopped:"), max_timeout=15) with open(self.working_dir + "/configs/prospector.yml", 'w') as f: diff --git a/metricbeat/mb/module/registry.go b/libbeat/cfgfile/registry.go similarity index 59% rename from metricbeat/mb/module/registry.go rename to libbeat/cfgfile/registry.go index 7884a0295fb..5bfcf556039 100644 --- a/metricbeat/mb/module/registry.go +++ b/libbeat/cfgfile/registry.go @@ -1,31 +1,31 @@ -package module +package cfgfile import "sync" -type registry struct { +type Registry struct { sync.Mutex List map[uint64]Runner } -func newRunningRegistry() *registry { - return ®istry{ +func NewRegistry() *Registry { + return &Registry{ List: map[uint64]Runner{}, } } -func (r *registry) Add(hash uint64, m Runner) { +func (r *Registry) Add(hash uint64, m Runner) { r.Lock() defer r.Unlock() r.List[hash] = m } -func (r *registry) Remove(hash uint64) { +func (r *Registry) Remove(hash uint64) { r.Lock() defer r.Unlock() delete(r.List, hash) } -func (r *registry) Has(hash uint64) bool { +func (r *Registry) Has(hash uint64) bool { r.Lock() defer r.Unlock() @@ -33,7 +33,7 @@ func (r *registry) Has(hash uint64) bool { return ok } -func (r *registry) CopyList() map[uint64]Runner { +func (r *Registry) CopyList() map[uint64]Runner { r.Lock() defer r.Unlock() diff --git a/libbeat/cfgfile/reload.go b/libbeat/cfgfile/reload.go index 44f398ccec1..1e8f6471710 100644 --- a/libbeat/cfgfile/reload.go +++ b/libbeat/cfgfile/reload.go @@ -1,12 +1,28 @@ package cfgfile -import "time" +import ( + "expvar" + "path/filepath" + "sync" + "time" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/paths" +) var ( DefaultReloadConfig = ReloadConfig{ Period: 10 * time.Second, Enabled: false, } + + debugf = logp.MakeDebug("cfgfile") + + configReloads = expvar.NewInt("libbeat.config.reloads") + moduleStarts = expvar.NewInt("libbeat.config.module.starts") + moduleStops = expvar.NewInt("libbeat.config.module.stops") + moduleRunning = expvar.NewInt("libbeat.config.module.running") ) type ReloadConfig struct { @@ -15,3 +31,164 @@ type ReloadConfig struct { Period time.Duration `config:"period"` Enabled bool `config:"enabled"` } + +type RunnerFactory interface { + Create(*common.Config) (Runner, error) +} + +type Runner interface { + Start() + Stop() + ID() uint64 +} + +// Reloader is used to register and reload modules +type Reloader struct { + registry *Registry + config ReloadConfig + done chan struct{} + wg sync.WaitGroup +} + +// NewReloader creates new Reloader instance for the given config +func NewReloader(cfg *common.Config) *Reloader { + + config := DefaultReloadConfig + cfg.Unpack(&config) + + return &Reloader{ + registry: NewRegistry(), + config: config, + done: make(chan struct{}), + } +} + +// Run runs the reloader +func (rl *Reloader) Run(runnerFactory RunnerFactory) { + + logp.Info("Config reloader started") + + rl.wg.Add(1) + defer rl.wg.Done() + + // Stop all running modules when method finishes + defer rl.stopRunners(rl.registry.CopyList()) + + path := rl.config.Path + if !filepath.IsAbs(path) { + path = paths.Resolve(paths.Config, path) + } + + gw := NewGlobWatcher(path) + + for { + select { + case <-rl.done: + logp.Info("Dynamic config reloader stopped") + return + case <-time.After(rl.config.Period): + + debugf("Scan for new config files") + configReloads.Add(1) + + files, updated, err := gw.Scan() + if err != nil { + // In most cases of error, updated == false, so will continue + // to next iteration below + logp.Err("Error fetching new config files: %v", err) + } + + // no file changes + if !updated { + continue + } + + // Load all config objects + configs := []*common.Config{} + for _, file := range files { + c, err := LoadList(file) + if err != nil { + logp.Err("Error loading config: %s", err) + continue + } + + configs = append(configs, c...) + } + + debugf("Number of module configs found: %v", len(configs)) + + startList := map[uint64]Runner{} + stopList := rl.registry.CopyList() + + for _, c := range configs { + + // Only add configs to startList which are enabled + if !c.Enabled() { + continue + } + + runner, err := runnerFactory.Create(c) + if err != nil { + logp.Err("Error creating module: %s", err) + continue + } + + debugf("Remove module from stoplist: %v", runner.ID()) + delete(stopList, runner.ID()) + + // As module already exist, it must be removed from the stop list and not started + if !rl.registry.Has(runner.ID()) { + debugf("Add module to startlist: %v", runner.ID()) + startList[runner.ID()] = runner + continue + } + } + + rl.stopRunners(stopList) + rl.startRunners(startList) + } + } +} + +// Stop stops the reloader and waits for all modules to properly stop +func (rl *Reloader) Stop() { + close(rl.done) + rl.wg.Wait() +} + +func (rl *Reloader) startRunners(list map[uint64]Runner) { + + logp.Info("Starting %v runners ...", len(list)) + for id, runner := range list { + runner.Start() + rl.registry.Add(id, runner) + + moduleStarts.Add(1) + moduleRunning.Add(1) + debugf("New runner started: %v", id) + } +} + +func (rl *Reloader) stopRunners(list map[uint64]Runner) { + logp.Info("Stopping %v runners ...", len(list)) + + wg := sync.WaitGroup{} + for hash, runner := range list { + wg.Add(1) + + // Stop modules in parallel + go func(h uint64, run Runner) { + defer func() { + moduleStops.Add(1) + moduleRunning.Add(-1) + debugf("Runner stopped: %v", h) + wg.Done() + }() + + run.Stop() + rl.registry.Remove(h) + }(hash, runner) + } + + wg.Wait() +} diff --git a/metricbeat/beater/metricbeat.go b/metricbeat/beater/metricbeat.go index 235fafd028d..08b7e18d716 100644 --- a/metricbeat/beater/metricbeat.go +++ b/metricbeat/beater/metricbeat.go @@ -10,6 +10,7 @@ import ( "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/module" + "github.com/elastic/beats/libbeat/cfgfile" "github.com/pkg/errors" ) @@ -73,13 +74,15 @@ func (bt *Metricbeat) Run(b *beat.Beat) error { if bt.config.ReloadModules.Enabled() { logp.Warn("EXPERIMENTAL feature dynamic configuration reloading is enabled.") - configReloader := module.NewReloader(bt.config.ReloadModules, b.Publisher) - go configReloader.Run() + moduleReloader := cfgfile.NewReloader(bt.config.ReloadModules) + factory := module.NewFactory(b.Publisher) + + go moduleReloader.Run(factory) wg.Add(1) go func() { defer wg.Done() <-bt.done - configReloader.Stop() + moduleReloader.Stop() }() } diff --git a/metricbeat/mb/module/factory.go b/metricbeat/mb/module/factory.go new file mode 100644 index 00000000000..4a89483e01b --- /dev/null +++ b/metricbeat/mb/module/factory.go @@ -0,0 +1,30 @@ +package module + +import ( + "github.com/elastic/beats/libbeat/cfgfile" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/publisher" + "github.com/elastic/beats/metricbeat/mb" +) + +// Factory is used to register and reload modules +type Factory struct { + client func() publisher.Client +} + +// NewFactory creates new Reloader instance for the given config +func NewFactory(p publisher.Publisher) *Factory { + return &Factory{ + client: p.Connect, + } +} + +func (r *Factory) Create(c *common.Config) (cfgfile.Runner, error) { + w, err := NewWrapper(c, mb.Registry) + if err != nil { + return nil, err + } + + mr := NewRunner(r.client, w) + return mr, nil +} diff --git a/metricbeat/mb/module/reload.go b/metricbeat/mb/module/reload.go deleted file mode 100644 index 184555ffdc6..00000000000 --- a/metricbeat/mb/module/reload.go +++ /dev/null @@ -1,174 +0,0 @@ -package module - -import ( - "expvar" - "path/filepath" - "sync" - "time" - - "github.com/elastic/beats/libbeat/cfgfile" - "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/logp" - "github.com/elastic/beats/libbeat/paths" - "github.com/elastic/beats/libbeat/publisher" - "github.com/elastic/beats/metricbeat/mb" -) - -var ( - configReloads = expvar.NewInt("metricbeat.config.reloads") - moduleStarts = expvar.NewInt("metricbeat.config.module.starts") - moduleStops = expvar.NewInt("metricbeat.config.module.stops") - moduleRunning = expvar.NewInt("metricbeat.config.module.running") -) - -// Reloader is used to register and reload modules -type Reloader struct { - registry *registry - config cfgfile.ReloadConfig - client func() publisher.Client - done chan struct{} - wg sync.WaitGroup -} - -// NewReloader creates new Reloader instance for the given config -func NewReloader(cfg *common.Config, p publisher.Publisher) *Reloader { - - config := cfgfile.DefaultReloadConfig - cfg.Unpack(&config) - - return &Reloader{ - registry: newRunningRegistry(), - config: config, - client: p.Connect, - done: make(chan struct{}), - } -} - -// Run runs the reloader -func (r *Reloader) Run() { - - logp.Info("Config reloader started") - - r.wg.Add(1) - defer r.wg.Done() - - // Stop all running modules when method finishes - defer r.stopModules(r.registry.CopyList()) - - path := r.config.Path - if !filepath.IsAbs(path) { - path = paths.Resolve(paths.Config, path) - } - - gw := cfgfile.NewGlobWatcher(path) - - for { - select { - case <-r.done: - logp.Info("Dynamic config reloader stopped") - return - case <-time.After(r.config.Period): - - debugf("Scan for new config files") - configReloads.Add(1) - - files, updated, err := gw.Scan() - if err != nil { - // In most cases of error, updated == false, so will continue - // to next iteration below - logp.Err("Error fetching new config files: %v", err) - } - - // no file changes - if !updated { - continue - } - - // Load all config objects - configs := []*common.Config{} - for _, file := range files { - c, err := cfgfile.LoadList(file) - if err != nil { - logp.Err("Error loading config: %s", err) - continue - } - - configs = append(configs, c...) - } - - // Check which configs do not exist anymore - s, err := NewWrappers(configs, mb.Registry) - if err != nil { - if err != mb.ErrAllModulesDisabled && err != mb.ErrEmptyConfig { - // Continuing as only some modules could have an error - logp.Err("Error creating modules: %s", err) - } - } - - debugf("Number of module wrappers created: %v", len(s)) - - var startList []*Wrapper - stopList := r.registry.CopyList() - - for _, w := range s { - - // Only add modules to startlist which are enabled - if !w.Config().Enabled { - continue - } - - debugf("Remove from stoplist: %v", w.Hash()) - delete(stopList, w.Hash()) - - // As module already exist, it must be removed from the stop list and not started - if !r.registry.Has(w.Hash()) { - debugf("Add to startlist: %v", w.Hash()) - startList = append(startList, w) - continue - } - } - - r.stopModules(stopList) - r.startModules(startList) - } - } -} - -// Stop stops the reloader and waits for all modules to properly stop -func (r *Reloader) Stop() { - close(r.done) - r.wg.Wait() -} - -func (r *Reloader) startModules(list []*Wrapper) { - - logp.Info("Starting %v modules ...", len(list)) - for _, mw := range list { - mr := NewRunner(r.client, mw) - mr.Start() - r.registry.Add(mw.Hash(), mr) - moduleStarts.Add(1) - moduleRunning.Add(1) - debugf("New Module Started: %v", mw.Hash()) - } -} - -func (r *Reloader) stopModules(list map[uint64]Runner) { - logp.Info("Stopping %v modules ...", len(list)) - - wg := sync.WaitGroup{} - for hash, w := range list { - wg.Add(1) - // Stop modules in parallel - func() { - defer wg.Done() - w.Stop() - r.registry.Remove(hash) - moduleStops.Add(1) - moduleRunning.Add(-1) - debugf("Module stopped: %v", hash) - }() - } - - wg.Wait() -} diff --git a/metricbeat/mb/module/runner.go b/metricbeat/mb/module/runner.go index 8e2352df733..939943c5679 100644 --- a/metricbeat/mb/module/runner.go +++ b/metricbeat/mb/module/runner.go @@ -17,6 +17,9 @@ type Runner interface { // publisher.Client will be closed by Stop. If Stop is called more than // once, only the first stop the Module and wait for it to exit. Stop() + + // Added to be consistent with cfgfile.Runner + ID() uint64 } // NewRunner returns a Runner facade. The events generated by @@ -57,3 +60,7 @@ func (mr *runner) Stop() { mr.wg.Wait() }) } + +func (mr *runner) ID() uint64 { + return mr.mod.Hash() +} diff --git a/metricbeat/tests/system/test_reload.py b/metricbeat/tests/system/test_reload.py index 40a88ba0ca4..f374a4283d1 100644 --- a/metricbeat/tests/system/test_reload.py +++ b/metricbeat/tests/system/test_reload.py @@ -66,7 +66,7 @@ def test_start_stop(self): # Wait until offset for new line is updated self.wait_until( - lambda: self.log_contains("Starting 1 modules"), + lambda: self.log_contains("Starting 1 runner"), max_timeout=10) self.wait_until(lambda: self.output_lines() > 0) @@ -76,7 +76,7 @@ def test_start_stop(self): # Wait until offset for new line is updated self.wait_until( - lambda: self.log_contains("Module stopped:"), + lambda: self.log_contains("Runner stopped:"), max_timeout=10) lines = self.output_lines() From 2d260be23835f56b011ec2c76594ab3788f666b4 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Sat, 4 Feb 2017 14:32:15 +0100 Subject: [PATCH 56/78] Removed edit_urls - no longer needed --- filebeat/docs/configuring-howto.asciidoc | 1 - filebeat/docs/index.asciidoc | 19 --------------- .../docs/reference/configuration.asciidoc | 1 - .../configuration/filebeat-options.asciidoc | 6 ----- filebeat/docs/securing-filebeat.asciidoc | 2 -- filebeat/docs/troubleshooting.asciidoc | 2 -- heartbeat/docs/configuring-howto.asciidoc | 1 - heartbeat/docs/index.asciidoc | 23 +++---------------- .../docs/reference/configuration.asciidoc | 1 - .../configuration/heartbeat-options.asciidoc | 6 ----- heartbeat/docs/securing-heartbeat.asciidoc | 2 -- heartbeat/docs/troubleshooting.asciidoc | 2 -- libbeat/docs/index.asciidoc | 11 --------- metricbeat/docs/configuring-howto.asciidoc | 1 - .../docs/developer-guide/index.asciidoc | 5 ---- metricbeat/docs/index.asciidoc | 19 --------------- .../docs/reference/configuration.asciidoc | 1 - .../configuration/metricbeat-options.asciidoc | 7 ------ metricbeat/docs/securing-metricbeat.asciidoc | 2 -- metricbeat/docs/troubleshooting.asciidoc | 2 -- packetbeat/docs/configuring-howto.asciidoc | 1 - packetbeat/docs/index.asciidoc | 21 ----------------- .../docs/reference/configuration.asciidoc | 1 - .../configuration/packetbeat-options.asciidoc | 7 ------ packetbeat/docs/securing-packetbeat.asciidoc | 2 -- packetbeat/docs/troubleshooting.asciidoc | 3 --- winlogbeat/docs/configuring-howto.asciidoc | 1 - winlogbeat/docs/index.asciidoc | 13 ----------- .../docs/reference/configuration.asciidoc | 1 - .../configuration/winlogbeat-options.asciidoc | 6 ----- winlogbeat/docs/securing-winlogbeat.asciidoc | 2 -- winlogbeat/docs/troubleshooting.asciidoc | 2 -- 32 files changed, 3 insertions(+), 171 deletions(-) diff --git a/filebeat/docs/configuring-howto.asciidoc b/filebeat/docs/configuring-howto.asciidoc index 2e950362de0..5fc2ac741d9 100644 --- a/filebeat/docs/configuring-howto.asciidoc +++ b/filebeat/docs/configuring-howto.asciidoc @@ -25,5 +25,4 @@ The following topics describe how to configure Filebeat: -- -pass::[] include::reference/configuration.asciidoc[] diff --git a/filebeat/docs/index.asciidoc b/filebeat/docs/index.asciidoc index 8d6067f75bc..a1a5633682e 100644 --- a/filebeat/docs/index.asciidoc +++ b/filebeat/docs/index.asciidoc @@ -16,60 +16,41 @@ include::../../libbeat/docs/version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./getting-started.asciidoc[] -pass::[] include::./command-line.asciidoc[] -pass::[] include::../../libbeat/docs/shared-directory-layout.asciidoc[] -pass::[] include::./upgrading.asciidoc[] -pass::[] include::./how-filebeat-works.asciidoc[] -pass::[] include::./configuring-howto.asciidoc[] -pass::[] include::./filebeat-filtering.asciidoc[] -pass::[] include::./multiline.asciidoc[] -pass::[] include::../../libbeat/docs/shared-config-ingest.asciidoc[] -pass::[] include::../../libbeat/docs/shared-env-vars.asciidoc[] -pass::[] include::./multiple-prospectors.asciidoc[] -pass::[] include::./load-balancing.asciidoc[] :allplatforms: -pass::[] include::../../libbeat/docs/yaml.asciidoc[] -pass::[] include::../../libbeat/docs/regexp.asciidoc[] -pass::[] include::./fields.asciidoc[] -pass::[] include::./securing-filebeat.asciidoc[] -pass::[] include::./troubleshooting.asciidoc[] -pass::[] include::./faq.asciidoc[] -pass::[] include::./migration.asciidoc[] diff --git a/filebeat/docs/reference/configuration.asciidoc b/filebeat/docs/reference/configuration.asciidoc index 46c883a7da9..9c92550d02e 100644 --- a/filebeat/docs/reference/configuration.asciidoc +++ b/filebeat/docs/reference/configuration.asciidoc @@ -25,7 +25,6 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> -pass::[] include::configuration/filebeat-options.asciidoc[] diff --git a/filebeat/docs/reference/configuration/filebeat-options.asciidoc b/filebeat/docs/reference/configuration/filebeat-options.asciidoc index a048747a01a..ec9e66fa064 100644 --- a/filebeat/docs/reference/configuration/filebeat-options.asciidoc +++ b/filebeat/docs/reference/configuration/filebeat-options.asciidoc @@ -577,23 +577,17 @@ Example configuration: filebeat.shutdown_timeout: 5s ------------------------------------------------------------------------------------- -pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] include::./reload-configuration.asciidoc[] -pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] -pass::[] include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/processors-config.asciidoc[] diff --git a/filebeat/docs/securing-filebeat.asciidoc b/filebeat/docs/securing-filebeat.asciidoc index 47856fa7633..d1ccfbb66ca 100644 --- a/filebeat/docs/securing-filebeat.asciidoc +++ b/filebeat/docs/securing-filebeat.asciidoc @@ -10,7 +10,6 @@ The following topics describe how to secure communication between Filebeat and o * <> //sets block macro for https.asciidoc included in next section -pass::[] -- @@ -20,7 +19,6 @@ pass::[] [[configuring-ssl-logstash]] == Securing Communication With Logstash by Using SSL diff --git a/filebeat/docs/troubleshooting.asciidoc b/filebeat/docs/troubleshooting.asciidoc index d4cf477c387..ea6f8a57131 100644 --- a/filebeat/docs/troubleshooting.asciidoc +++ b/filebeat/docs/troubleshooting.asciidoc @@ -11,7 +11,6 @@ following tips: * <> //sets block macro for getting-help.asciidoc included in next section -pass::[] -- @@ -21,7 +20,6 @@ pass::[] [[enable-filebeat-debugging]] == Debugging diff --git a/heartbeat/docs/configuring-howto.asciidoc b/heartbeat/docs/configuring-howto.asciidoc index d71fb8d03e8..8ca477ce9a3 100644 --- a/heartbeat/docs/configuring-howto.asciidoc +++ b/heartbeat/docs/configuring-howto.asciidoc @@ -27,5 +27,4 @@ The following topics describe how to configure Heartbeat: -- -pass::[] include::reference/configuration.asciidoc[] diff --git a/heartbeat/docs/index.asciidoc b/heartbeat/docs/index.asciidoc index bd5147209ab..9ab1f782795 100644 --- a/heartbeat/docs/index.asciidoc +++ b/heartbeat/docs/index.asciidoc @@ -17,58 +17,41 @@ include::../../libbeat/docs/version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./getting-started.asciidoc[] -pass::[] include::./command-line.asciidoc[] -pass::[] include::../../libbeat/docs/shared-directory-layout.asciidoc[] -//pass::[] - +// //include::./upgrading.asciidoc[] -//pass::[] - +// //include::./how-heartbeat-works.asciidoc[] -pass::[] include::./configuring-howto.asciidoc[] -pass::[] include::./heartbeat-filtering.asciidoc[] -pass::[] include::../../libbeat/docs/shared-config-ingest.asciidoc[] //points to shared topic because configuring-logstash.asciidoc is just a wrapper -pass::[] include::./configuring-logstash.asciidoc[] -pass::[] include::../../libbeat/docs/shared-env-vars.asciidoc[] :allplatforms: -pass::[] include::../../libbeat/docs/yaml.asciidoc[] -pass::[] include::../../libbeat/docs/regexp.asciidoc[] -pass::[] include::./fields.asciidoc[] -pass::[] include::./securing-heartbeat.asciidoc[] -pass::[] include::./troubleshooting.asciidoc[] -pass::[] include::./faq.asciidoc[] -// pass::[] - +// //include::./heartbeat-devguide.asciidoc[] diff --git a/heartbeat/docs/reference/configuration.asciidoc b/heartbeat/docs/reference/configuration.asciidoc index 1726e1fc77b..e5e258ef21f 100644 --- a/heartbeat/docs/reference/configuration.asciidoc +++ b/heartbeat/docs/reference/configuration.asciidoc @@ -23,7 +23,6 @@ configuration settings, you need to restart Heartbeat to pick up the changes. * <> * <> -pass::[] include::configuration/heartbeat-options.asciidoc[] diff --git a/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc b/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc index ac845e29bd6..a4144a02eb1 100644 --- a/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc +++ b/heartbeat/docs/reference/configuration/heartbeat-options.asciidoc @@ -446,21 +446,15 @@ specify for `limit` should be below the configured ulimit. The timezone for the scheduler. By default the scheduler uses localtime. -pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] -pass::[] include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/processors-config.asciidoc[] diff --git a/heartbeat/docs/securing-heartbeat.asciidoc b/heartbeat/docs/securing-heartbeat.asciidoc index 82a74d64c09..fe777ebe8d8 100644 --- a/heartbeat/docs/securing-heartbeat.asciidoc +++ b/heartbeat/docs/securing-heartbeat.asciidoc @@ -11,7 +11,6 @@ and other products in the Elastic stack: * <> //sets block macro for https.asciidoc included in next section -pass::[] -- @@ -21,7 +20,6 @@ pass::[] [[configuring-ssl-logstash]] == Securing Communication With Logstash by Using SSL diff --git a/heartbeat/docs/troubleshooting.asciidoc b/heartbeat/docs/troubleshooting.asciidoc index 3e6c51e6550..2eba169918e 100644 --- a/heartbeat/docs/troubleshooting.asciidoc +++ b/heartbeat/docs/troubleshooting.asciidoc @@ -11,7 +11,6 @@ following tips: * <> //sets block macro for getting-help.asciidoc included in next section -pass::[] -- @@ -21,7 +20,6 @@ pass::[] [[enable-heartbeat-debugging]] == Debugging diff --git a/libbeat/docs/index.asciidoc b/libbeat/docs/index.asciidoc index 6891330e0f5..620efdf5eda 100644 --- a/libbeat/docs/index.asciidoc +++ b/libbeat/docs/index.asciidoc @@ -17,39 +17,28 @@ include::./version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./communitybeats.asciidoc[] -pass::[] include::./gettingstarted.asciidoc[] -pass::[] include::./installing-beats.asciidoc[] -pass::[] include::./repositories.asciidoc[] -pass::[] include::./breaking.asciidoc[] -pass::[] include::./upgrading.asciidoc[] -pass::[] include::./config-file-format.asciidoc[] pass::[] -pass::[] include::./newbeat.asciidoc[] -pass::[] include::./event-conventions.asciidoc[] -pass::[] include::./newdashboards.asciidoc[] pass::[] -pass::[] include::./release.asciidoc[] diff --git a/metricbeat/docs/configuring-howto.asciidoc b/metricbeat/docs/configuring-howto.asciidoc index a983d4d2c79..3cd8c7b6ba0 100644 --- a/metricbeat/docs/configuring-howto.asciidoc +++ b/metricbeat/docs/configuring-howto.asciidoc @@ -95,5 +95,4 @@ metricbeat: -- -pass::[] include::reference/configuration.asciidoc[] diff --git a/metricbeat/docs/developer-guide/index.asciidoc b/metricbeat/docs/developer-guide/index.asciidoc index 04861893de7..b2f3fa1bb46 100644 --- a/metricbeat/docs/developer-guide/index.asciidoc +++ b/metricbeat/docs/developer-guide/index.asciidoc @@ -58,17 +58,12 @@ directly from the service itself and not via a third-party tool. The goal is to have as few movable parts as possible and for Metricbeat to run as close as possible to the service that it needs to monitor. -pass::[] include::./create-metricset.asciidoc[] -pass::[] include::./metricset-details.asciidoc[] -pass::[] include::./create-module.asciidoc[] -pass::[] include::./creating-beat-from-metricbeat.asciidoc[] -pass::[] include::./faq.asciidoc[] diff --git a/metricbeat/docs/index.asciidoc b/metricbeat/docs/index.asciidoc index e81ceebbc9d..cd049484c54 100644 --- a/metricbeat/docs/index.asciidoc +++ b/metricbeat/docs/index.asciidoc @@ -13,60 +13,41 @@ include::../../libbeat/docs/version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./gettingstarted.asciidoc[] -pass::[] include::./command-line.asciidoc[] -pass::[] include::../../libbeat/docs/shared-directory-layout.asciidoc[] -pass::[] include::./upgrading.asciidoc[] -pass::[] include::./how-metricbeat-works.asciidoc[] -pass::[] include::./metricbeat-in-a-container.asciidoc[] -pass::[] include::./configuring-howto.asciidoc[] -pass::[] include::./metricbeat-filtering.asciidoc[] -pass::[] include::../../libbeat/docs/shared-config-ingest.asciidoc[] -pass::[] include::./configuring-logstash.asciidoc[] -pass::[] include::../../libbeat/docs/shared-env-vars.asciidoc[] :allplatforms: -pass::[] include::../../libbeat/docs/yaml.asciidoc[] -pass::[] include::../../libbeat/docs/regexp.asciidoc[] -pass::[] include::./modules.asciidoc[] -pass::[] include::./fields.asciidoc[] -pass::[] include::./securing-metricbeat.asciidoc[] -pass::[] include::./troubleshooting.asciidoc[] -pass::[] include::./faq.asciidoc[] -pass::[] include::./developer-guide/index.asciidoc[] diff --git a/metricbeat/docs/reference/configuration.asciidoc b/metricbeat/docs/reference/configuration.asciidoc index 56d88fabb33..1e7e48f6878 100644 --- a/metricbeat/docs/reference/configuration.asciidoc +++ b/metricbeat/docs/reference/configuration.asciidoc @@ -22,5 +22,4 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> -pass::[] include::configuration/metricbeat-options.asciidoc[] diff --git a/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc b/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc index 3dce78cfd82..8521a9c0dba 100644 --- a/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc +++ b/metricbeat/docs/reference/configuration/metricbeat-options.asciidoc @@ -68,24 +68,17 @@ A list of filters to apply to the data generated by the module. -pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] -pass::[] include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] -pass::[] include::./reload-configuration.asciidoc[] -pass::[] include::../../../../libbeat/docs/processors-config.asciidoc[] diff --git a/metricbeat/docs/securing-metricbeat.asciidoc b/metricbeat/docs/securing-metricbeat.asciidoc index 89f682b33b2..8c92870c772 100644 --- a/metricbeat/docs/securing-metricbeat.asciidoc +++ b/metricbeat/docs/securing-metricbeat.asciidoc @@ -11,7 +11,6 @@ The following topics describe how to secure communication between * <> //sets block macro for https.asciidoc included in next section -pass::[] -- @@ -20,7 +19,6 @@ pass::[] [[configuring-ssl-logstash]] == Securing Communication With Logstash by Using SSL diff --git a/metricbeat/docs/troubleshooting.asciidoc b/metricbeat/docs/troubleshooting.asciidoc index fb4c00e42fc..8c578683d2b 100644 --- a/metricbeat/docs/troubleshooting.asciidoc +++ b/metricbeat/docs/troubleshooting.asciidoc @@ -10,7 +10,6 @@ If you have issues installing or running {beatname_uc}, read the following tips: * <> //sets block macro for getting-help.asciidoc included in next section -pass::[] -- @@ -20,7 +19,6 @@ pass::[] [[enable-metricbeat-debugging]] == Debugging diff --git a/packetbeat/docs/configuring-howto.asciidoc b/packetbeat/docs/configuring-howto.asciidoc index 6958c992775..5029ec50ee9 100644 --- a/packetbeat/docs/configuring-howto.asciidoc +++ b/packetbeat/docs/configuring-howto.asciidoc @@ -27,6 +27,5 @@ The following topics describe how to configure Packetbeat: -- -pass::[] include::reference/configuration.asciidoc[] diff --git a/packetbeat/docs/index.asciidoc b/packetbeat/docs/index.asciidoc index 6c6c56aa16c..12d6b9289bd 100644 --- a/packetbeat/docs/index.asciidoc +++ b/packetbeat/docs/index.asciidoc @@ -19,71 +19,50 @@ include::../../libbeat/docs/version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./gettingstarted.asciidoc[] -pass::[] include::./command-line.asciidoc[] -pass::[] include::../../libbeat/docs/shared-directory-layout.asciidoc[] -pass::[] include::./upgrading.asciidoc[] -pass::[] include::./configuring-howto.asciidoc[] -pass::[] include::./packetbeat-filtering.asciidoc[] -pass::[] include::../../libbeat/docs/shared-config-ingest.asciidoc[] -pass::[] include::./packetbeat-geoip.asciidoc[] -pass::[] include::./configuring-logstash.asciidoc[] -pass::[] include::../../libbeat/docs/shared-env-vars.asciidoc[] -pass::[] include::./capturing.asciidoc[] -pass::[] include::./thrift.asciidoc[] -pass::[] include::./maintaining-topology.asciidoc[] :allplatforms: -pass::[] include::../../libbeat/docs/yaml.asciidoc[] -pass::[] include::./fields.asciidoc[] -pass::[] include::./securing-packetbeat.asciidoc[] -pass::[] include::./visualizing-data-packetbeat.asciidoc[] -pass::[] include::./filtering.asciidoc[] -pass::[] include::./troubleshooting.asciidoc[] -pass::[] include::./faq.asciidoc[] pass::[] -pass::[] include::./new_protocol.asciidoc[] pass::[] diff --git a/packetbeat/docs/reference/configuration.asciidoc b/packetbeat/docs/reference/configuration.asciidoc index 709ed29c7b6..18f1b8d6716 100644 --- a/packetbeat/docs/reference/configuration.asciidoc +++ b/packetbeat/docs/reference/configuration.asciidoc @@ -29,7 +29,6 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes NOTE: Packetbeat maintains a real-time topology map of all the servers in your network. See <> for more details. -pass::[] include::configuration/packetbeat-options.asciidoc[] diff --git a/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc b/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc index f0a49bc6130..317e60b4792 100644 --- a/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc +++ b/packetbeat/docs/reference/configuration/packetbeat-options.asciidoc @@ -792,25 +792,18 @@ periodically afterwards, it scans the process table for processes that match the values specified for this option. The match is done against the process' command line as read from `/proc//cmdline`. -pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] -pass::[] include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] -pass::[] include::./runconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/processors-config.asciidoc[] diff --git a/packetbeat/docs/securing-packetbeat.asciidoc b/packetbeat/docs/securing-packetbeat.asciidoc index 4d047256443..c40a0483f22 100644 --- a/packetbeat/docs/securing-packetbeat.asciidoc +++ b/packetbeat/docs/securing-packetbeat.asciidoc @@ -10,7 +10,6 @@ The following topics describe how to secure communication between Packetbeat and * <> //sets block macro for https.asciidoc included in next section -pass::[] -- @@ -19,7 +18,6 @@ pass::[] [[configuring-ssl-logstash]] == Securing Communication With Logstash by Using SSL diff --git a/packetbeat/docs/troubleshooting.asciidoc b/packetbeat/docs/troubleshooting.asciidoc index fcad073f5f7..132ede8e86d 100644 --- a/packetbeat/docs/troubleshooting.asciidoc +++ b/packetbeat/docs/troubleshooting.asciidoc @@ -12,7 +12,6 @@ following tips: * <> //sets block macro for getting-help.asciidoc included in next section -pass::[] -- @@ -22,7 +21,6 @@ pass::[] [[enable-packetbeat-debugging]] == Debugging @@ -30,7 +28,6 @@ pass::[] [[recording-trace]] == Recording a Trace diff --git a/winlogbeat/docs/configuring-howto.asciidoc b/winlogbeat/docs/configuring-howto.asciidoc index b70694409dc..fb8dd4f0b91 100644 --- a/winlogbeat/docs/configuring-howto.asciidoc +++ b/winlogbeat/docs/configuring-howto.asciidoc @@ -21,6 +21,5 @@ The following topics describe how to configure Winlogbeat: -- -pass::[] include::reference/configuration.asciidoc[] diff --git a/winlogbeat/docs/index.asciidoc b/winlogbeat/docs/index.asciidoc index 29bb79c4f80..53437fbccc6 100644 --- a/winlogbeat/docs/index.asciidoc +++ b/winlogbeat/docs/index.asciidoc @@ -16,42 +16,29 @@ include::../../libbeat/docs/version.asciidoc[] include::./overview.asciidoc[] -pass::[] include::./getting-started.asciidoc[] -pass::[] include::./command-line.asciidoc[] -pass::[] include::../../libbeat/docs/shared-directory-layout.asciidoc[] -pass::[] include::./upgrading.asciidoc[] -pass::[] include::./configuring-howto.asciidoc[] -pass::[] include::./winlogbeat-filtering.asciidoc[] -pass::[] include::../../libbeat/docs/shared-config-ingest.asciidoc[] -pass::[] include::../../libbeat/docs/shared-env-vars.asciidoc[] :win: -pass::[] include::../../libbeat/docs/yaml.asciidoc[] -pass::[] include::./fields.asciidoc[] -pass::[] include::./securing-winlogbeat.asciidoc[] -pass::[] include::./troubleshooting.asciidoc[] -pass::[] include::./faq.asciidoc[] diff --git a/winlogbeat/docs/reference/configuration.asciidoc b/winlogbeat/docs/reference/configuration.asciidoc index 0335ee93974..54bc344e367 100644 --- a/winlogbeat/docs/reference/configuration.asciidoc +++ b/winlogbeat/docs/reference/configuration.asciidoc @@ -23,7 +23,6 @@ configuration settings, you need to restart {beatname_uc} to pick up the changes * <> * <> -pass::[] include::configuration/winlogbeat-options.asciidoc[] diff --git a/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc b/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc index b97b9b56074..531c839f9d3 100644 --- a/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc +++ b/winlogbeat/docs/reference/configuration/winlogbeat-options.asciidoc @@ -349,21 +349,15 @@ The metrics are served as a JSON document. The metrics include: - total number of successfully published events - uptime -pass::[] include::../../../../libbeat/docs/generalconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/outputconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/shared-path-config.asciidoc[] -pass::[] include::../../../../libbeat/docs/dashboardsconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/loggingconfig.asciidoc[] -pass::[] include::../../../../libbeat/docs/processors-config.asciidoc[] diff --git a/winlogbeat/docs/securing-winlogbeat.asciidoc b/winlogbeat/docs/securing-winlogbeat.asciidoc index 60d382a7298..09f2c3f035a 100644 --- a/winlogbeat/docs/securing-winlogbeat.asciidoc +++ b/winlogbeat/docs/securing-winlogbeat.asciidoc @@ -10,7 +10,6 @@ The following topics describe how to secure communication between Winlogbeat and * <> //sets block macro for https.asciidoc included in next section -pass::[] -- @@ -19,7 +18,6 @@ pass::[] [[configuring-ssl-logstash]] == Securing Communication With Logstash by Using SSL diff --git a/winlogbeat/docs/troubleshooting.asciidoc b/winlogbeat/docs/troubleshooting.asciidoc index b972f9a0aee..8a08e470924 100644 --- a/winlogbeat/docs/troubleshooting.asciidoc +++ b/winlogbeat/docs/troubleshooting.asciidoc @@ -10,7 +10,6 @@ If you have issues installing or running Winlogbeat, read the following tips: * <> //sets block macro for getting-help.asciidoc included in next section -pass::[] -- @@ -20,7 +19,6 @@ pass::[] [[enable-winlogbeat-debugging]] From cde1e46871d6cde8e855b1dd57dfc1d2179ed8a0 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Sun, 5 Feb 2017 19:17:17 +0100 Subject: [PATCH 57/78] Update Windows paths for chocolatey installed packages (#3524) Done for: - nginx - apache Mysql seems to use windows event logs by default. Syslog doesn't really exist on Windows. Currently no paths are defined, which result in an error when starting Filebeat. Part of #3159. --- filebeat/module/apache2/access/manifest.yml | 3 ++- filebeat/module/apache2/error/manifest.yml | 3 ++- filebeat/module/nginx/access/manifest.yml | 2 +- filebeat/module/nginx/error/manifest.yml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/filebeat/module/apache2/access/manifest.yml b/filebeat/module/apache2/access/manifest.yml index bb625476f3b..3383f48990e 100644 --- a/filebeat/module/apache2/access/manifest.yml +++ b/filebeat/module/apache2/access/manifest.yml @@ -8,7 +8,8 @@ var: os.darwin: - /usr/local/var/log/apache2/access_log* os.windows: - - "C:/Program Files/Apache Software Foundation/Apache2.4/logs/access.log*" + - "C:/tools/Apache/httpd-2.*/Apache24/logs/access.log*" + - "C:/Program Files/Apache Software Foundation/Apache2.*/logs/access.log*" - name: pipeline # options: with_plugins, no_plugins default: with_plugins diff --git a/filebeat/module/apache2/error/manifest.yml b/filebeat/module/apache2/error/manifest.yml index f19f149f92f..2de0969bda8 100644 --- a/filebeat/module/apache2/error/manifest.yml +++ b/filebeat/module/apache2/error/manifest.yml @@ -7,7 +7,8 @@ var: os.darwin: - /usr/local/var/log/apache2/error_log* os.windows: - - "C:/Program Files/Apache Software Foundation/Apache2.4/logs/error.log*" + - "C:/tools/Apache/httpd-2.*/Apache24/logs/error.log*" + - "C:/Program Files/Apache Software Foundation/Apache2.*/logs/error.log*" ingest_pipeline: ingest/pipeline.json prospector: config/error.yml diff --git a/filebeat/module/nginx/access/manifest.yml b/filebeat/module/nginx/access/manifest.yml index bdcfb28ea94..20ff3039d5a 100644 --- a/filebeat/module/nginx/access/manifest.yml +++ b/filebeat/module/nginx/access/manifest.yml @@ -7,7 +7,7 @@ var: os.darwin: - /usr/local/var/log/nginx/access.log* os.windows: - - c:/programfiles/nginx/logs/access.log* + - c:/programdata/nginx/logs/*access.log* - name: pipeline # options: with_plugins, no_plugins default: with_plugins diff --git a/filebeat/module/nginx/error/manifest.yml b/filebeat/module/nginx/error/manifest.yml index 4577ad6f2ab..427763bdc4b 100644 --- a/filebeat/module/nginx/error/manifest.yml +++ b/filebeat/module/nginx/error/manifest.yml @@ -7,7 +7,7 @@ var: os.darwin: - /usr/local/var/log/nginx/error.log* os.windows: - - c:/programfiles/nginx/logs/error.log* + - c:/programdata/nginx/logs/error.log* ingest_pipeline: ingest/pipeline.json prospector: config/nginx-error.yml From b8beed163da995ce9757d351a095e87f09877b1c Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Sun, 5 Feb 2017 13:35:04 -0500 Subject: [PATCH 58/78] Enable cgroup metric collection by default (#3519) On Linux cgroup metric collection will be enabled by default. The feature is no longer labeled as experimental. The configuration option has been renamed from `cgroups` to `process.cgroups.enabled`. --- CHANGELOG.asciidoc | 3 +++ metricbeat/docs/fields.asciidoc | 1 - .../docs/metricbeat-in-a-container.asciidoc | 22 +++++++++---------- metricbeat/docs/modules/system.asciidoc | 9 ++++---- metricbeat/metricbeat.full.yml | 4 ++-- .../module/system/_meta/config.full.yml | 4 ++-- metricbeat/module/system/_meta/docs.asciidoc | 9 ++++---- .../module/system/process/_meta/docs.asciidoc | 7 +++--- .../module/system/process/_meta/fields.yml | 2 -- metricbeat/module/system/process/process.go | 11 +++++----- .../module/system/socket/_meta/docs.asciidoc | 16 +++++++------- metricbeat/tests/system/test_system.py | 6 ++++- 12 files changed, 49 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 10dce15ced1..b10eb383739 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -17,6 +17,9 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Change beat generator. Use `$GOPATH/src/github.com/elastic/beats/script/generate.py` to generate a beat. {pull}3452[3452] *Metricbeat* +- Linux cgroup metrics are now enabled by default for the system process + metricset. The configuration option for the feature was renamed from + `cgroups` to `process.cgroups.enabled`. {pull}3519[3519] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index a80b8042c0a..5f1370d7cf8 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -6305,7 +6305,6 @@ The hard limit on the number of file descriptors opened by the process. The hard [float] == cgroup Fields -experimental[] Metrics and limits from the cgroup of which the task is a member. cgroup metrics are reported when the process has membership in a non-root cgroup. These metrics are only available on Linux. diff --git a/metricbeat/docs/metricbeat-in-a-container.asciidoc b/metricbeat/docs/metricbeat-in-a-container.asciidoc index 173291b3bef..b4b081be0bc 100644 --- a/metricbeat/docs/metricbeat-in-a-container.asciidoc +++ b/metricbeat/docs/metricbeat-in-a-container.asciidoc @@ -4,7 +4,7 @@ Elastic does not provide any official container images for Metricbeat. The examples on this page assume you are using your own Metricbeat container image. -When executing Metricbeat in a container, there are some important +When executing Metricbeat in a container, there are some important things to be aware of if you want to monitor the host machine or other containers. Let's walk-through some examples using Docker as our container orchestration tool. @@ -18,14 +18,14 @@ work properly inside of a container. This enables Metricbeat to monitor the host machine from within the container. [source,sh] ---- +---- sudo docker run \ --volume=/proc:/hostfs/proc:ro \ <1> --volume=/sys/fs/cgroup:/hostfs/sys/fs/cgroup:ro \ <2> --volume=/:/hostfs:ro \ <3> --net=host <4> my/metricbeat:latest -system.hostfs=/hostfs ---- +---- <1> Metricbeat's <> collects much of its data through the Linux proc filesystem, which is normally located at `/proc`. Because containers @@ -34,10 +34,10 @@ container's `/proc` is different than the host's `/proc`. To account for this, y can mount the host's `/proc` filesystem inside of the container and tell Metricbeat to look inside the `/hostfs` directory when looking for `/proc` by using the `-system.hostfs=/hostfs` CLI flag. -<2> If you have enabled cgroup reporting (an experimental feature) from the -<>, then you need to mount the host's cgroup mountpoints -within the container. They need to be mounted inside the directory specified by -the `-system.hostfs` CLI flag. +<2> If cgroup reporting is enabled for the +<>, then you need +to mount the host's cgroup mountpoints within the container. They need to be +mounted inside the directory specified by the `-system.hostfs` CLI flag. <3> If you want to be able to monitor filesystems from the host by using the <>, then those filesystems need to be mounted inside of the container. They can be mounted at any location. @@ -55,12 +55,12 @@ Next let's look at an example of monitoring a containerized service from a Metricbeat container. [source,sh] ---- +---- sudo docker run \ --link some-mysql:mysql \ <1> -e MYSQL_PASSWORD=secret \ <2> my/metricbeat:latest ---- +---- <1> Linking the containers enables Metricbeat access the exposed ports of the mysql container, and it makes the hostname `mysql` resolvable to Metricbeat. @@ -71,14 +71,14 @@ variables or as command line flags to Metricbeat (see the `-E` CLI flag in < username: root password: ${MYSQL_PASSWORD} <2> ---- +---- <1> The `mysql` hostname will resolve to the `some-mysql` container's address. <2> The `MYSQL_PASSWORD` variable will be evaluated at startup. If the variable diff --git a/metricbeat/docs/modules/system.asciidoc b/metricbeat/docs/modules/system.asciidoc index 97116990035..afbcb259e54 100644 --- a/metricbeat/docs/modules/system.asciidoc +++ b/metricbeat/docs/modules/system.asciidoc @@ -26,17 +26,18 @@ metricbeat.modules: metricsets: ["process"] processes: ['.*'] ---- -*`cgroups`*:: When the `process` metricset is enabled, you can use the boolean -`cgroups` option to enable the experimental cgroup metrics on Linux. +*`process.cgroups.enabled`*:: When the `process` metricset is enabled, you can +use this boolean configuration option to disable cgroup metrics. By default +cgroup metrics collection is enabled. + -The following example config enables cgroups metrics on Linux. +The following example config disables cgroup metrics on Linux. + [source,yaml] ---- metricbeat.modules: - module: system metricsets: ["process"] - cgroups: true + process.cgroups.enabled: false ---- *`cpu_ticks`*:: When the `cpu` or `core` metricset is enabled, you can specify `cpu_ticks: true` to report CPU ticks in addition to CPU percentages stats. For example: + diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 83081503769..a66a49d36c3 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -64,8 +64,8 @@ metricbeat.modules: # if true, exports the CPU usage in ticks, together with the percentage values #cpu_ticks: false - # EXPERIMENTAL: cgroups can be enabled for the process metricset. - #cgroups: false + # Enable collection of cgroup metrics from processes on Linux. + #process.cgroups.enabled: true # A list of regular expressions used to whitelist environment variables # reported with the process metricset's events. Defaults to empty. diff --git a/metricbeat/module/system/_meta/config.full.yml b/metricbeat/module/system/_meta/config.full.yml index 8884a946386..b6b511c7157 100644 --- a/metricbeat/module/system/_meta/config.full.yml +++ b/metricbeat/module/system/_meta/config.full.yml @@ -36,8 +36,8 @@ # if true, exports the CPU usage in ticks, together with the percentage values #cpu_ticks: false - # EXPERIMENTAL: cgroups can be enabled for the process metricset. - #cgroups: false + # Enable collection of cgroup metrics from processes on Linux. + #process.cgroups.enabled: true # A list of regular expressions used to whitelist environment variables # reported with the process metricset's events. Defaults to empty. diff --git a/metricbeat/module/system/_meta/docs.asciidoc b/metricbeat/module/system/_meta/docs.asciidoc index 037adcdaf5e..a861aa9e5d5 100644 --- a/metricbeat/module/system/_meta/docs.asciidoc +++ b/metricbeat/module/system/_meta/docs.asciidoc @@ -21,17 +21,18 @@ metricbeat.modules: metricsets: ["process"] processes: ['.*'] ---- -*`cgroups`*:: When the `process` metricset is enabled, you can use the boolean -`cgroups` option to enable the experimental cgroup metrics on Linux. +*`process.cgroups.enabled`*:: When the `process` metricset is enabled, you can +use this boolean configuration option to disable cgroup metrics. By default +cgroup metrics collection is enabled. + -The following example config enables cgroups metrics on Linux. +The following example config disables cgroup metrics on Linux. + [source,yaml] ---- metricbeat.modules: - module: system metricsets: ["process"] - cgroups: true + process.cgroups.enabled: false ---- *`cpu_ticks`*:: When the `cpu` or `core` metricset is enabled, you can specify `cpu_ticks: true` to report CPU ticks in addition to CPU percentages stats. For example: + diff --git a/metricbeat/module/system/process/_meta/docs.asciidoc b/metricbeat/module/system/process/_meta/docs.asciidoc index 618898cc13f..bea87f87c81 100644 --- a/metricbeat/module/system/process/_meta/docs.asciidoc +++ b/metricbeat/module/system/process/_meta/docs.asciidoc @@ -13,8 +13,7 @@ This metricset is available on: [float] === Control Group (cgroup) Metrics -On Linux this metricset can collect metrics from any cgroups that the process -is a member of. This feature is currently experimental, and it is not enabled by -default so you must add `cgroup: true` to the system module configuration to -enable it. +On Linux this metricset will collect metrics from any cgroups that the process +is a member of. This feature is enabled by default and can be disabled by adding +`process.cgroup.enabled: false` to the system module configuration. diff --git a/metricbeat/module/system/process/_meta/fields.yml b/metricbeat/module/system/process/_meta/fields.yml index d3cb2fcece8..1696d4e0d6b 100644 --- a/metricbeat/module/system/process/_meta/fields.yml +++ b/metricbeat/module/system/process/_meta/fields.yml @@ -116,8 +116,6 @@ - name: cgroup type: group description: > - experimental[] - Metrics and limits from the cgroup of which the task is a member. cgroup metrics are reported when the process has membership in a non-root cgroup. These metrics are only available on Linux. diff --git a/metricbeat/module/system/process/process.go b/metricbeat/module/system/process/process.go index ca3081e43f4..d6c6839dbb1 100644 --- a/metricbeat/module/system/process/process.go +++ b/metricbeat/module/system/process/process.go @@ -34,12 +34,11 @@ type MetricSet struct { // New creates and returns a new MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { config := struct { - Procs []string `config:"processes"` // collect all processes by default - Cgroups bool `config:"cgroups"` + Procs []string `config:"processes"` + Cgroups *bool `config:"process.cgroups.enabled"` EnvWhitelist []string `config:"process.env.whitelist"` }{ - Procs: []string{".*"}, - Cgroups: false, + Procs: []string{".*"}, // collect all processes by default } if err := base.Module().UnpackConfig(&config); err != nil { return nil, err @@ -63,8 +62,8 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, fmt.Errorf("unexpected module type") } - if config.Cgroups { - logp.Warn("EXPERIMENTAL: Cgroup is enabled for the system.process MetricSet.") + if config.Cgroups == nil || *config.Cgroups { + debugf("process cgroup data collection is enabled") m.cgroup, err = cgroup.NewReader(systemModule.HostFS, true) if err != nil { return nil, errors.Wrap(err, "error initializing cgroup reader") diff --git a/metricbeat/module/system/socket/_meta/docs.asciidoc b/metricbeat/module/system/socket/_meta/docs.asciidoc index 86868b3fc4e..77184bc9dd3 100644 --- a/metricbeat/module/system/socket/_meta/docs.asciidoc +++ b/metricbeat/module/system/socket/_meta/docs.asciidoc @@ -8,20 +8,20 @@ The system `socket` metricset reports an event for each new TCP socket that it sees. It does this by polling the kernel periodically to get a dump of all sockets. You set the polling interval by configuring the `period` option. Specifying a short polling interval with this metricset is important to avoid -missing short-lived connections. For example: +missing short-lived connections. For example: [source,yaml] ---- +---- metricbeat.modules: - module: system metricsets: [cpu, memory] -- module: system +- module: system metricsets: [socket] <1> period: 1s ---- +---- <1> You can configure the `socket` metricset separately to specify a different -`period` value than the other metricsets. +`period` value than the other metricsets. The metricset reports the process that has the socket open. In order to provide this information, Metricbeat must be running as root. Root access is also @@ -30,16 +30,16 @@ required to read the file descriptor information of other processes. You can configure the metricset to perform a reverse lookup on the remote IP, and the returned hostname will be added to the event and cached. If a hostname is found, then the eTLD+1 (effective top-level domain plus one level) value will -also be added to the event. Reverse lookups are disabled by default. +also be added to the event. Reverse lookups are disabled by default. The following example shows the full configuration for the metricset along with the defaults. [source,yaml] ---- +---- - module: system metricsets: [socket] socket.reverse_lookup.enabled: false socket.reverse_lookup.success_ttl: 60s socket.reverse_lookup.failure_ttl: 60s ---- +---- diff --git a/metricbeat/tests/system/test_system.py b/metricbeat/tests/system/test_system.py index 5e0b1eb2ab0..15248b55061 100644 --- a/metricbeat/tests/system/test_system.py +++ b/metricbeat/tests/system/test_system.py @@ -37,8 +37,9 @@ # cmdline is also part of the system process fields, but it may not be present # for some kernel level processes. fd is also part of the system process, but # is not available on all OSes and requires root to read for all processes. +# cgroup is only available on linux. SYSTEM_PROCESS_FIELDS = ["cpu", "memory", "name", "pid", "ppid", "pgid", - "state", "username"] + "state", "username", "cgroup"] class SystemTest(metricbeat.BaseTest): @@ -325,6 +326,9 @@ def test_process(self): """ Test system/process output. """ + if not sys.platform.startswith("linux") and "cgroup" in SYSTEM_PROCESS_FIELDS: + SYSTEM_PROCESS_FIELDS.remove("cgroup") + self.render_config_template(modules=[{ "name": "system", "metricsets": ["process"], From 541278a70985da48a2bedb93fedf0768c668ecf6 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Sun, 5 Feb 2017 13:37:10 -0500 Subject: [PATCH 59/78] Disable date detection in index templates (#3528) We use dynamic fields in some places and date detection is enabled by default so if a Beat sends a dynamic string field that looks like a date, the field mapping will be date. This means that all follow on data using the same field name must also be a date or else a field mapping exception will occur. Disabling date detection will make the generated index mappings more predicable. Closes #3389 --- CHANGELOG.asciidoc | 1 + filebeat/filebeat.template-es2x.json | 1 + filebeat/filebeat.template.json | 1 + heartbeat/heartbeat.template-es2x.json | 1 + heartbeat/heartbeat.template.json | 1 + libbeat/scripts/generate_template.py | 1 + metricbeat/metricbeat.template-es2x.json | 1 + metricbeat/metricbeat.template.json | 1 + packetbeat/packetbeat.template-es2x.json | 1 + packetbeat/packetbeat.template.json | 1 + winlogbeat/winlogbeat.template-es2x.json | 1 + winlogbeat/winlogbeat.template.json | 1 + 12 files changed, 12 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b10eb383739..66381845518 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -72,6 +72,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add the option to pass custom HTTP headers to the Elasticsearch output. {pull}3400[3400] - Unify `regexp` and `contains` conditionals, for both to support array of strings and convert numbers to strings if required. {pull}3469[3469] - Add the option to load the sample dashboards during the Beat startup phase. {pull}3506[3506] +- Disabled date detection in Elasticsearch index templates. Date fields must be explicitly defined in index templates. {pull}3528[3528] *Metricbeat* diff --git a/filebeat/filebeat.template-es2x.json b/filebeat/filebeat.template-es2x.json index 6c4fcd2ef2b..b22d17b688f 100644 --- a/filebeat/filebeat.template-es2x.json +++ b/filebeat/filebeat.template-es2x.json @@ -9,6 +9,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/filebeat/filebeat.template.json b/filebeat/filebeat.template.json index a38e64921fa..31de71d46b2 100644 --- a/filebeat/filebeat.template.json +++ b/filebeat/filebeat.template.json @@ -4,6 +4,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/heartbeat/heartbeat.template-es2x.json b/heartbeat/heartbeat.template-es2x.json index e89bdf296df..bc9d5fd3e66 100644 --- a/heartbeat/heartbeat.template-es2x.json +++ b/heartbeat/heartbeat.template-es2x.json @@ -9,6 +9,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/heartbeat/heartbeat.template.json b/heartbeat/heartbeat.template.json index 8cca9814722..94b19a69fd4 100644 --- a/heartbeat/heartbeat.template.json +++ b/heartbeat/heartbeat.template.json @@ -4,6 +4,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/libbeat/scripts/generate_template.py b/libbeat/scripts/generate_template.py index 7756c080d97..8abf5d5190c 100644 --- a/libbeat/scripts/generate_template.py +++ b/libbeat/scripts/generate_template.py @@ -50,6 +50,7 @@ def fields_to_es_template(args, input, output, index, version): }, "mappings": { "_default_": { + "date_detection": False, "properties": {}, "_meta": { "version": version, diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index e49291cbc95..e5c04c1df85 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -9,6 +9,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 8286409179e..0f30cffa972 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -4,6 +4,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/packetbeat/packetbeat.template-es2x.json b/packetbeat/packetbeat.template-es2x.json index f44b4f49f88..ddd2e1634e5 100644 --- a/packetbeat/packetbeat.template-es2x.json +++ b/packetbeat/packetbeat.template-es2x.json @@ -9,6 +9,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/packetbeat/packetbeat.template.json b/packetbeat/packetbeat.template.json index 5d9eb6e253d..7806d09c84d 100644 --- a/packetbeat/packetbeat.template.json +++ b/packetbeat/packetbeat.template.json @@ -4,6 +4,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/winlogbeat/winlogbeat.template-es2x.json b/winlogbeat/winlogbeat.template-es2x.json index cb20ddb213d..36f8a604245 100644 --- a/winlogbeat/winlogbeat.template-es2x.json +++ b/winlogbeat/winlogbeat.template-es2x.json @@ -9,6 +9,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { diff --git a/winlogbeat/winlogbeat.template.json b/winlogbeat/winlogbeat.template.json index 54653adaaf3..d7376851057 100644 --- a/winlogbeat/winlogbeat.template.json +++ b/winlogbeat/winlogbeat.template.json @@ -4,6 +4,7 @@ "_meta": { "version": "6.0.0-alpha1" }, + "date_detection": false, "dynamic_templates": [ { "strings_as_keyword": { From 722d515105f95e9104f369ecb375451cb653c99a Mon Sep 17 00:00:00 2001 From: "Amanda H. L. de Andrade" Date: Mon, 6 Feb 2017 14:11:00 -0200 Subject: [PATCH 60/78] Disk Metricsets for CEPH module (#3499) --- CHANGELOG.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 96 +++++++++++++++++++ metricbeat/docs/modules/ceph.asciidoc | 10 +- .../docs/modules/ceph/cluster_disk.asciidoc | 19 ++++ .../docs/modules/ceph/pool_disk.asciidoc | 19 ++++ metricbeat/include/list.go | 2 + metricbeat/metricbeat.full.yml | 2 +- metricbeat/metricbeat.template-es2x.json | 61 ++++++++++++ metricbeat/metricbeat.template.json | 60 ++++++++++++ metricbeat/module/ceph/_meta/config.yml | 2 +- .../_meta/testdata/df_sample_response.json | 92 ++++++++++++++++++ .../module/ceph/cluster_disk/_meta/data.json | 29 ++++++ .../ceph/cluster_disk/_meta/docs.asciidoc | 3 + .../module/ceph/cluster_disk/_meta/fields.yml | 20 ++++ .../module/ceph/cluster_disk/cluster_disk.go | 55 +++++++++++ .../cluster_disk_integration_test.go | 47 +++++++++ .../ceph/cluster_disk/cluster_disk_test.go | 49 ++++++++++ metricbeat/module/ceph/cluster_disk/data.go | 44 +++++++++ .../module/ceph/pool_disk/_meta/data.json | 29 ++++++ .../module/ceph/pool_disk/_meta/docs.asciidoc | 3 + .../module/ceph/pool_disk/_meta/fields.yml | 31 ++++++ metricbeat/module/ceph/pool_disk/data.go | 63 ++++++++++++ metricbeat/module/ceph/pool_disk/pool_disk.go | 55 +++++++++++ .../pool_disk/pool_disk_integration_test.go | 47 +++++++++ .../module/ceph/pool_disk/pool_disk_test.go | 54 +++++++++++ metricbeat/tests/system/test_ceph.py | 44 +++++++++ 26 files changed, 934 insertions(+), 3 deletions(-) create mode 100644 metricbeat/docs/modules/ceph/cluster_disk.asciidoc create mode 100644 metricbeat/docs/modules/ceph/pool_disk.asciidoc create mode 100644 metricbeat/module/ceph/_meta/testdata/df_sample_response.json create mode 100644 metricbeat/module/ceph/cluster_disk/_meta/data.json create mode 100644 metricbeat/module/ceph/cluster_disk/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/cluster_disk/_meta/fields.yml create mode 100644 metricbeat/module/ceph/cluster_disk/cluster_disk.go create mode 100644 metricbeat/module/ceph/cluster_disk/cluster_disk_integration_test.go create mode 100644 metricbeat/module/ceph/cluster_disk/cluster_disk_test.go create mode 100644 metricbeat/module/ceph/cluster_disk/data.go create mode 100644 metricbeat/module/ceph/pool_disk/_meta/data.json create mode 100644 metricbeat/module/ceph/pool_disk/_meta/docs.asciidoc create mode 100644 metricbeat/module/ceph/pool_disk/_meta/fields.yml create mode 100644 metricbeat/module/ceph/pool_disk/data.go create mode 100644 metricbeat/module/ceph/pool_disk/pool_disk.go create mode 100644 metricbeat/module/ceph/pool_disk/pool_disk_integration_test.go create mode 100644 metricbeat/module/ceph/pool_disk/pool_disk_test.go diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 66381845518..3453fc6086a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -100,6 +100,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - System module uses new matchers for white-listing processes. {pull}3469[3469] - Add CEPH module with health metricset. {pull}3311[3311] - Add php_fpm module with pool metricset. {pull}3415[3415] +- Add cluster_disk and pool_disk metricsets to CEPH module. {pull}3499[3499] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 5f1370d7cf8..c4f1040b32c 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -427,6 +427,43 @@ experimental[] ceph Module +[float] +== cluster_disk Fields + +cluster_disk + + + +[float] +=== ceph.cluster_disk.available.bytes + +type: long + +format: bytes + +Available bytes of the cluster + + +[float] +=== ceph.cluster_disk.total.bytes + +type: long + +format: bytes + +Total bytes of the cluster + + +[float] +=== ceph.cluster_disk.used.bytes + +type: long + +format: bytes + +Used bytes of the cluster + + [float] == cluster_health Fields @@ -577,6 +614,65 @@ type: long Last updated +[float] +== pool_disk Fields + +pool_disk + + + +[float] +=== ceph.pool_disk.id + +type: long + +Id of the pool + + +[float] +=== ceph.pool_disk.name + +type: keyword + +Name of the pool + + +[float] +=== ceph.pool_disk.stats.available.bytes + +type: long + +format: bytes + +Available bytes of the pool + + +[float] +=== ceph.pool_disk.stats.objects + +type: long + +Number of objects of the pool + + +[float] +=== ceph.pool_disk.stats.used.bytes + +type: long + +format: bytes + +Used bytes of the pool + + +[float] +=== ceph.pool_disk.stats.used.kb + +type: long + +Used kb of the pool + + [[exported-fields-cloud]] == Cloud Provider Metadata Fields diff --git a/metricbeat/docs/modules/ceph.asciidoc b/metricbeat/docs/modules/ceph.asciidoc index 17377a79684..bd4963baee8 100644 --- a/metricbeat/docs/modules/ceph.asciidoc +++ b/metricbeat/docs/modules/ceph.asciidoc @@ -19,7 +19,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: #- module: ceph -# metricsets: ["cluster_health", "monitor_health"] +# metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] # enabled: true # period: 10s # hosts: ["localhost:5000"] @@ -30,11 +30,19 @@ metricbeat.modules: The following metricsets are available: +* <> + * <> * <> +* <> + +include::ceph/cluster_disk.asciidoc[] + include::ceph/cluster_health.asciidoc[] include::ceph/monitor_health.asciidoc[] +include::ceph/pool_disk.asciidoc[] + diff --git a/metricbeat/docs/modules/ceph/cluster_disk.asciidoc b/metricbeat/docs/modules/ceph/cluster_disk.asciidoc new file mode 100644 index 00000000000..c3dac350a7a --- /dev/null +++ b/metricbeat/docs/modules/ceph/cluster_disk.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-ceph-cluster_disk]] +include::../../../module/ceph/cluster_disk/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/ceph/cluster_disk/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules/ceph/pool_disk.asciidoc b/metricbeat/docs/modules/ceph/pool_disk.asciidoc new file mode 100644 index 00000000000..81e9f57ad11 --- /dev/null +++ b/metricbeat/docs/modules/ceph/pool_disk.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-ceph-pool_disk]] +include::../../../module/ceph/pool_disk/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/ceph/pool_disk/_meta/data.json[] +---- diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index c80c67e525c..68f6d060d4b 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -11,8 +11,10 @@ import ( _ "github.com/elastic/beats/metricbeat/module/apache" _ "github.com/elastic/beats/metricbeat/module/apache/status" _ "github.com/elastic/beats/metricbeat/module/ceph" + _ "github.com/elastic/beats/metricbeat/module/ceph/cluster_disk" _ "github.com/elastic/beats/metricbeat/module/ceph/cluster_health" _ "github.com/elastic/beats/metricbeat/module/ceph/monitor_health" + _ "github.com/elastic/beats/metricbeat/module/ceph/pool_disk" _ "github.com/elastic/beats/metricbeat/module/couchbase" _ "github.com/elastic/beats/metricbeat/module/couchbase/bucket" _ "github.com/elastic/beats/metricbeat/module/couchbase/cluster" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index a66a49d36c3..4a4e17bd1c3 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -96,7 +96,7 @@ metricbeat.modules: #-------------------------------- ceph Module -------------------------------- #- module: ceph -# metricsets: ["cluster_health", "monitor_health"] +# metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] # enabled: true # period: 10s # hosts: ["localhost:5000"] diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index e5c04c1df85..03cdfebdf0d 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -196,6 +196,31 @@ }, "ceph": { "properties": { + "cluster_disk": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, "cluster_health": { "properties": { "overall_status": { @@ -299,6 +324,42 @@ } } } + }, + "pool_disk": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "stats": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "objects": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "kb": { + "type": "long" + } + } + } + } + } + } } } }, diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 0f30cffa972..316dae56668 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -197,6 +197,31 @@ }, "ceph": { "properties": { + "cluster_disk": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, "cluster_health": { "properties": { "overall_status": { @@ -296,6 +321,41 @@ } } } + }, + "pool_disk": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "objects": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "kb": { + "type": "long" + } + } + } + } + } + } } } }, diff --git a/metricbeat/module/ceph/_meta/config.yml b/metricbeat/module/ceph/_meta/config.yml index f33044c8c0d..408e5876bf5 100644 --- a/metricbeat/module/ceph/_meta/config.yml +++ b/metricbeat/module/ceph/_meta/config.yml @@ -1,5 +1,5 @@ #- module: ceph -# metricsets: ["cluster_health", "monitor_health"] +# metricsets: ["cluster_disk", "cluster_health", "monitor_health", "pool_disk"] # enabled: true # period: 10s # hosts: ["localhost:5000"] diff --git a/metricbeat/module/ceph/_meta/testdata/df_sample_response.json b/metricbeat/module/ceph/_meta/testdata/df_sample_response.json new file mode 100644 index 00000000000..0cf66d78c98 --- /dev/null +++ b/metricbeat/module/ceph/_meta/testdata/df_sample_response.json @@ -0,0 +1,92 @@ +{ + "status":"OK", + "output":{ + "pools":[ + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":0, + "kb_used":0 + }, + "name":"rbd", + "id":0 + }, + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":0, + "kb_used":0 + }, + "name":"cephfs_data", + "id":1 + }, + { + "stats":{ + "bytes_used":2068, + "max_avail":5003444224, + "objects":20, + "kb_used":3 + }, + "name":"cephfs_metadata", + "id":2 + }, + { + "stats":{ + "bytes_used":1636, + "max_avail":5003444224, + "objects":4, + "kb_used":2 + }, + "name":".rgw.root", + "id":3 + }, + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":8, + "kb_used":0 + }, + "name":"default.rgw.control", + "id":4 + }, + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":0, + "kb_used":0 + }, + "name":"default.rgw.data.root", + "id":5 + }, + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":0, + "kb_used":0 + }, + "name":"default.rgw.gc", + "id":6 + }, + { + "stats":{ + "bytes_used":0, + "max_avail":5003444224, + "objects":0, + "kb_used":0 + }, + "name":"default.rgw.log", + "id":7 + } + ], + "stats":{ + "total_used_bytes":1428520960, + "total_bytes":6431965184, + "total_avail_bytes":5003444224 + } + } +} diff --git a/metricbeat/module/ceph/cluster_disk/_meta/data.json b/metricbeat/module/ceph/cluster_disk/_meta/data.json new file mode 100644 index 00000000000..cbcbd76c991 --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/_meta/data.json @@ -0,0 +1,29 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "ceph": { + "df": { + "stats": { + "available": { + "bytes": 5040500736 + }, + "total": { + "bytes": 6431965184 + }, + "used": { + "bytes": 1391464448 + } + } + } + }, + "metricset": { + "host": "ceph:5000", + "module": "ceph", + "name": "df", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/cluster_disk/_meta/docs.asciidoc b/metricbeat/module/ceph/cluster_disk/_meta/docs.asciidoc new file mode 100644 index 00000000000..4709f85b3cb --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== ceph cluster_disk MetricSet + +This is the cluster_disk metricset of the module ceph. diff --git a/metricbeat/module/ceph/cluster_disk/_meta/fields.yml b/metricbeat/module/ceph/cluster_disk/_meta/fields.yml new file mode 100644 index 00000000000..7ed03c022dd --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/_meta/fields.yml @@ -0,0 +1,20 @@ +- name: cluster_disk + type: group + description: > + cluster_disk + fields: + - name: available.bytes + type: long + description: > + Available bytes of the cluster + format: bytes + - name: total.bytes + type: long + description: > + Total bytes of the cluster + format: bytes + - name: used.bytes + type: long + description: > + Used bytes of the cluster + format: bytes diff --git a/metricbeat/module/ceph/cluster_disk/cluster_disk.go b/metricbeat/module/ceph/cluster_disk/cluster_disk.go new file mode 100644 index 00000000000..daae302e70a --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/cluster_disk.go @@ -0,0 +1,55 @@ +package cluster_disk + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/api/v0.1/df" +) + +var ( + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() +) + +func init() { + if err := mb.Registry.AddMetricSet("ceph", "cluster_disk", New, hostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + *helper.HTTP +} + +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The ceph cluster_disk metricset is experimental") + + http := helper.NewHTTP(base) + http.SetHeader("Accept", "application/json") + + return &MetricSet{ + base, + http, + }, nil +} + +func (m *MetricSet) Fetch() (common.MapStr, error) { + + content, err := m.HTTP.FetchContent() + + if err != nil { + return nil, err + } + + return eventMapping(content), nil +} diff --git a/metricbeat/module/ceph/cluster_disk/cluster_disk_integration_test.go b/metricbeat/module/ceph/cluster_disk/cluster_disk_integration_test.go new file mode 100644 index 00000000000..8cfd9ccf4e0 --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/cluster_disk_integration_test.go @@ -0,0 +1,47 @@ +package cluster_disk + +import ( + "fmt" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + "os" + "testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventFetcher(t, getConfig()) + err := mbtest.WriteEvent(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"cluster_disk"}, + "hosts": getTestCephHost(), + } +} + +const ( + cephDefaultHost = "127.0.0.1" + cephDefaultPort = "5000" +) + +func getTestCephHost() string { + return fmt.Sprintf("%v:%v", + getenv("CEPH_HOST", cephDefaultHost), + getenv("CEPH_PORT", cephDefaultPort), + ) +} + +func getenv(name, defaultValue string) string { + return strDefault(os.Getenv(name), defaultValue) +} + +func strDefault(a, defaults string) string { + if len(a) == 0 { + return defaults + } + return a +} diff --git a/metricbeat/module/ceph/cluster_disk/cluster_disk_test.go b/metricbeat/module/ceph/cluster_disk/cluster_disk_test.go new file mode 100644 index 00000000000..24ca658143d --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/cluster_disk_test.go @@ -0,0 +1,49 @@ +package cluster_disk + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/common" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestFetchEventContents(t *testing.T) { + absPath, err := filepath.Abs("../_meta/testdata/") + assert.NoError(t, err) + + response, err := ioutil.ReadFile(absPath + "/df_sample_response.json") + assert.NoError(t, err) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "appication/json;") + w.Write([]byte(response)) + })) + defer server.Close() + + config := map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"cluster_disk"}, + "hosts": []string{server.URL}, + } + + f := mbtest.NewEventFetcher(t, config) + event, err := f.Fetch() + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + used := event["used"].(common.MapStr) + assert.EqualValues(t, 1428520960, used["bytes"]) + + total := event["total"].(common.MapStr) + assert.EqualValues(t, 6431965184, total["bytes"]) + + available := event["available"].(common.MapStr) + assert.EqualValues(t, 5003444224, available["bytes"]) +} diff --git a/metricbeat/module/ceph/cluster_disk/data.go b/metricbeat/module/ceph/cluster_disk/data.go new file mode 100644 index 00000000000..f567c8d632c --- /dev/null +++ b/metricbeat/module/ceph/cluster_disk/data.go @@ -0,0 +1,44 @@ +package cluster_disk + +import ( + "encoding/json" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type StatsCluster struct { + TotalUsedBytes int64 `json:"total_used_bytes"` + TotalBytes int64 `json:"total_bytes"` + TotalAvailBytes int64 `json:"total_avail_bytes"` +} + +type Output struct { + StatsCluster StatsCluster `json:"stats"` +} + +type DfRequest struct { + Status string `json:"status"` + Output Output `json:"output"` +} + +func eventMapping(content []byte) common.MapStr { + + var d DfRequest + err := json.Unmarshal(content, &d) + if err != nil { + logp.Err("Error: ", err) + } + + return common.MapStr{ + "used": common.MapStr{ + "bytes": d.Output.StatsCluster.TotalUsedBytes, + }, + "total": common.MapStr{ + "bytes": d.Output.StatsCluster.TotalBytes, + }, + "available": common.MapStr{ + "bytes": d.Output.StatsCluster.TotalAvailBytes, + }, + } +} diff --git a/metricbeat/module/ceph/pool_disk/_meta/data.json b/metricbeat/module/ceph/pool_disk/_meta/data.json new file mode 100644 index 00000000000..cbcbd76c991 --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/_meta/data.json @@ -0,0 +1,29 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "ceph": { + "df": { + "stats": { + "available": { + "bytes": 5040500736 + }, + "total": { + "bytes": 6431965184 + }, + "used": { + "bytes": 1391464448 + } + } + } + }, + "metricset": { + "host": "ceph:5000", + "module": "ceph", + "name": "df", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/ceph/pool_disk/_meta/docs.asciidoc b/metricbeat/module/ceph/pool_disk/_meta/docs.asciidoc new file mode 100644 index 00000000000..cb12795c757 --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/_meta/docs.asciidoc @@ -0,0 +1,3 @@ +=== ceph pool_disk MetricSet + +This is the pool_disk metricset of the module ceph. diff --git a/metricbeat/module/ceph/pool_disk/_meta/fields.yml b/metricbeat/module/ceph/pool_disk/_meta/fields.yml new file mode 100644 index 00000000000..7bc326524cd --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/_meta/fields.yml @@ -0,0 +1,31 @@ +- name: pool_disk + type: group + description: > + pool_disk + fields: + - name: id + type: long + description: > + Id of the pool + - name: name + type: keyword + description: > + Name of the pool + - name: stats.available.bytes + type: long + description: > + Available bytes of the pool + format: bytes + - name: stats.objects + type: long + description: > + Number of objects of the pool + - name: stats.used.bytes + type: long + description: > + Used bytes of the pool + format: bytes + - name: stats.used.kb + type: long + description: > + Used kb of the pool diff --git a/metricbeat/module/ceph/pool_disk/data.go b/metricbeat/module/ceph/pool_disk/data.go new file mode 100644 index 00000000000..b374702d1ec --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/data.go @@ -0,0 +1,63 @@ +package pool_disk + +import ( + "encoding/json" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" +) + +type Stats struct { + BytesUsed int64 `json:"bytes_used"` + MaxAvail int64 `json:"max_avail"` + Objects int64 `json:"objects"` + KbUsed int64 `json:"kb_used"` +} + +type Pool struct { + Id int64 `json:"id"` + Name string `json:"name"` + Stats Stats `json:"stats"` +} + +type Output struct { + Pools []Pool `json:"pools"` +} + +type DfRequest struct { + Status string `json:"status"` + Output Output `json:"output"` +} + +func eventsMapping(content []byte) []common.MapStr { + + var d DfRequest + err := json.Unmarshal(content, &d) + if err != nil { + logp.Err("Error: ", err) + } + + events := []common.MapStr{} + + for _, Pool := range d.Output.Pools { + event := common.MapStr{ + "name": Pool.Name, + "id": Pool.Id, + "stats": common.MapStr{ + "used": common.MapStr{ + "bytes": Pool.Stats.BytesUsed, + "kb": Pool.Stats.KbUsed, + }, + "available": common.MapStr{ + "bytes": Pool.Stats.MaxAvail, + }, + "objects": Pool.Stats.Objects, + }, + } + + events = append(events, event) + + } + + return events +} diff --git a/metricbeat/module/ceph/pool_disk/pool_disk.go b/metricbeat/module/ceph/pool_disk/pool_disk.go new file mode 100644 index 00000000000..2c24a42f112 --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/pool_disk.go @@ -0,0 +1,55 @@ +package pool_disk + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/helper" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/api/v0.1/df" +) + +var ( + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() +) + +func init() { + if err := mb.Registry.AddMetricSet("ceph", "pool_disk", New, hostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + *helper.HTTP +} + +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The ceph pool_disk metricset is experimental") + + http := helper.NewHTTP(base) + http.SetHeader("Accept", "application/json") + + return &MetricSet{ + base, + http, + }, nil +} + +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + + content, err := m.HTTP.FetchContent() + + if err != nil { + return nil, err + } + + return eventsMapping(content), nil +} diff --git a/metricbeat/module/ceph/pool_disk/pool_disk_integration_test.go b/metricbeat/module/ceph/pool_disk/pool_disk_integration_test.go new file mode 100644 index 00000000000..b7fead7b893 --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/pool_disk_integration_test.go @@ -0,0 +1,47 @@ +package pool_disk + +import ( + "fmt" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + "os" + "testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"pool_disk"}, + "hosts": getTestCephHost(), + } +} + +const ( + cephDefaultHost = "127.0.0.1" + cephDefaultPort = "5000" +) + +func getTestCephHost() string { + return fmt.Sprintf("%v:%v", + getenv("CEPH_HOST", cephDefaultHost), + getenv("CEPH_PORT", cephDefaultPort), + ) +} + +func getenv(name, defaultValue string) string { + return strDefault(os.Getenv(name), defaultValue) +} + +func strDefault(a, defaults string) string { + if len(a) == 0 { + return defaults + } + return a +} diff --git a/metricbeat/module/ceph/pool_disk/pool_disk_test.go b/metricbeat/module/ceph/pool_disk/pool_disk_test.go new file mode 100644 index 00000000000..3f63984c33d --- /dev/null +++ b/metricbeat/module/ceph/pool_disk/pool_disk_test.go @@ -0,0 +1,54 @@ +package pool_disk + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/common" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + "github.com/stretchr/testify/assert" +) + +func TestFetchEventContents(t *testing.T) { + absPath, err := filepath.Abs("../_meta/testdata/") + + response, err := ioutil.ReadFile(absPath + "/df_sample_response.json") + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "appication/json;") + w.Write([]byte(response)) + })) + defer server.Close() + + config := map[string]interface{}{ + "module": "ceph", + "metricsets": []string{"pool_disk"}, + "hosts": []string{server.URL}, + } + + f := mbtest.NewEventsFetcher(t, config) + events, err := f.Fetch() + event := events[0] + if !assert.NoError(t, err) { + t.FailNow() + } + + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) + + assert.EqualValues(t, "rbd", event["name"]) + assert.EqualValues(t, 0, event["id"]) + + stats := event["stats"].(common.MapStr) + + used := stats["used"].(common.MapStr) + assert.EqualValues(t, 0, used["bytes"]) + assert.EqualValues(t, 0, used["kb"]) + + available := stats["available"].(common.MapStr) + assert.EqualValues(t, 5003444224, available["bytes"]) + +} diff --git a/metricbeat/tests/system/test_ceph.py b/metricbeat/tests/system/test_ceph.py index b740f6f73f8..6cb70a46daa 100644 --- a/metricbeat/tests/system/test_ceph.py +++ b/metricbeat/tests/system/test_ceph.py @@ -4,6 +4,28 @@ class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_cluster_disk(self): + """ + ceph cluster_disk metricset test + """ + self.render_config_template(modules=[{ + "name": "ceph", + "metricsets": ["cluster_disk"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") def test_cluster_health(self): """ @@ -48,6 +70,28 @@ def test_monitor_health(self): self.assert_fields_are_documented(evt) + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_pool_disk(self): + """ + ceph pool_disk metricset test + """ + self.render_config_template(modules=[{ + "name": "ceph", + "metricsets": ["pool_disk"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + def get_hosts(self): return [os.getenv('CEPH_HOST', 'localhost') + ':' + os.getenv('CEPH_PORT', '5000')] From 5d89c282252e21253380cc1bc1bbacc81ec1e699 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Mon, 6 Feb 2017 17:13:32 +0100 Subject: [PATCH 61/78] Create all environments based on docker files in the modules (#3526) * Move all docker image specific configs into the modules by using a Dockerfile for each module * Move environment variables to module and link it from the composer file * Remove duplicated code for `wait_for` function This change will make it possible to partially auto generate the compose file and it brings the environment config closer to the module itself. --- libbeat/scripts/docker-entrypoint.sh | 24 +---- libbeat/scripts/wait_for.sh | 22 ++++ metricbeat/docker-compose.yml | 101 +++++++----------- metricbeat/docker-entrypoint.sh | 20 +--- metricbeat/module/apache/_meta/env | 2 + metricbeat/module/ceph/_meta/Dockerfile | 6 ++ metricbeat/module/ceph/_meta/env | 2 + metricbeat/module/couchbase/_meta/env | 3 + metricbeat/module/haproxy/_meta/env | 2 + metricbeat/module/kafka/_meta/Dockerfile | 5 + metricbeat/module/kafka/_meta/env | 2 + metricbeat/module/mongodb/_meta/Dockerfile | 1 + metricbeat/module/mongodb/_meta/env | 2 + metricbeat/module/mysql/_meta/Dockerfile | 3 + metricbeat/module/mysql/_meta/env | 3 + metricbeat/module/nginx/_meta/env | 2 + metricbeat/module/php_fpm/_meta/env | 2 + metricbeat/module/postgresql/_meta/Dockerfile | 1 + metricbeat/module/postgresql/_meta/env | 4 + metricbeat/module/prometheus/_meta/Dockerfile | 3 + metricbeat/module/prometheus/_meta/env | 2 + metricbeat/module/redis/_meta/Dockerfile | 1 + metricbeat/module/redis/_meta/env | 2 + metricbeat/module/zookeeper/_meta/Dockerfile | 1 + metricbeat/module/zookeeper/_meta/env | 2 + 25 files changed, 116 insertions(+), 102 deletions(-) create mode 100644 libbeat/scripts/wait_for.sh create mode 100644 metricbeat/module/apache/_meta/env create mode 100644 metricbeat/module/ceph/_meta/Dockerfile create mode 100644 metricbeat/module/ceph/_meta/env create mode 100644 metricbeat/module/couchbase/_meta/env create mode 100644 metricbeat/module/haproxy/_meta/env create mode 100644 metricbeat/module/kafka/_meta/Dockerfile create mode 100644 metricbeat/module/kafka/_meta/env create mode 100644 metricbeat/module/mongodb/_meta/Dockerfile create mode 100644 metricbeat/module/mongodb/_meta/env create mode 100644 metricbeat/module/mysql/_meta/Dockerfile create mode 100644 metricbeat/module/mysql/_meta/env create mode 100644 metricbeat/module/nginx/_meta/env create mode 100644 metricbeat/module/php_fpm/_meta/env create mode 100644 metricbeat/module/postgresql/_meta/Dockerfile create mode 100644 metricbeat/module/postgresql/_meta/env create mode 100644 metricbeat/module/prometheus/_meta/Dockerfile create mode 100644 metricbeat/module/prometheus/_meta/env create mode 100644 metricbeat/module/redis/_meta/Dockerfile create mode 100644 metricbeat/module/redis/_meta/env create mode 100644 metricbeat/module/zookeeper/_meta/Dockerfile create mode 100644 metricbeat/module/zookeeper/_meta/env diff --git a/libbeat/scripts/docker-entrypoint.sh b/libbeat/scripts/docker-entrypoint.sh index 44e7c2cd876..e9a144caf31 100755 --- a/libbeat/scripts/docker-entrypoint.sh +++ b/libbeat/scripts/docker-entrypoint.sh @@ -5,6 +5,11 @@ set -e # verify that all services are running before executing the command provided # to the docker container. +BASEDIR=$(dirname "$0") + +source $BASEDIR/wait_for.sh + + setDefaults() { # Use default ports and hosts if not specified. : ${ES_HOST:=localhost} @@ -65,25 +70,6 @@ waitForElasticsearch() { exit 1 } -# Wait for. Params: host, port, service -waitFor() { - echo -n "Waiting for ${3}(${1}:${2}) to start." - for ((i=1; i<=90; i++)) do - if nc -vz ${1} ${2} 2>/dev/null; then - echo - echo "${3} is ready!" - return 0 - fi - - ((i++)) - echo -n '.' - sleep 1 - done - - echo - echo >&2 "${3} is not available" - echo >&2 "Address: ${1}:${2}" -} # Main setDefaults diff --git a/libbeat/scripts/wait_for.sh b/libbeat/scripts/wait_for.sh new file mode 100644 index 00000000000..bbe23181af0 --- /dev/null +++ b/libbeat/scripts/wait_for.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Wait for. Params: host, port, service +waitFor() { + echo -n "Waiting for ${3}(${1}:${2}) to start." + for ((i=1; i<=90; i++)) do + if nc -vz ${1} ${2} 2>/dev/null; then + echo + echo "${3} is ready!" + return 0 + fi + + ((i++)) + echo -n '.' + sleep 1 + done + + echo + echo >&2 "${3} is not available" + echo >&2 "Address: ${1}:${2}" +} diff --git a/metricbeat/docker-compose.yml b/metricbeat/docker-compose.yml index 085ec954d13..ff41f65678e 100644 --- a/metricbeat/docker-compose.yml +++ b/metricbeat/docker-compose.yml @@ -2,6 +2,17 @@ version: '2' services: beat: build: ${PWD}/. + environment: + - TEST_ENVIRONMENT=false + working_dir: /go/src/github.com/elastic/beats/metricbeat + volumes: + - ${PWD}/..:/go/src/github.com/elastic/beats/ + # This is required for the docker module tests + - /var/run/docker.sock:/var/run/docker.sock + command: make + entrypoint: /go/src/github.com/elastic/beats/metricbeat/docker-entrypoint.sh + + # Module specific dependencies depends_on: - apache - ceph @@ -16,96 +27,58 @@ services: - prometheus - redis - zookeeper - environment: - - APACHE_HOST=apache - - APACHE_PORT=80 - - CEPH_HOST=ceph - - CEPH_PORT=5000 - - COUCHBASE_HOST=couchbase - - COUCHBASE_PORT=8091 - - COUCHBASE_DSN=http://Administrator:password@couchbase:8091 - - HAPROXY_HOST=haproxy - - HAPROXY_PORT=14567 - - KAFKA_HOST=kafka - - KAFKA_PORT=9092 - - NGINX_HOST=nginx - - NGINX_PORT=80 - - REDIS_HOST=redis - - REDIS_PORT=6379 - - MONGODB_HOST=mongodb - - MONGODB_PORT=27017 - - MYSQL_DSN=root:test@tcp(mysql:3306)/ - - MYSQL_HOST=mysql - - MYSQL_PORT=3306 - - PHPFPM_HOST=phpfpm - - PHPFPM_PORT=81 - - POSTGRESQL_DSN=postgres://postgresql:5432?sslmode=disable - - POSTGRESQL_HOST=postgresql - - POSTGRESQL_PORT=5432 - - POSTGRESQL_USERNAME=postgres - - PROMETHEUS_HOST=prometheus - - PROMETHEUS_PORT=9090 - - ZOOKEEPER_HOST=zookeeper - - ZOOKEEPER_PORT=2181 - - TEST_ENVIRONMENT=false - working_dir: /go/src/github.com/elastic/beats/metricbeat - volumes: - - ${PWD}/..:/go/src/github.com/elastic/beats/ - - /var/run/docker.sock:/var/run/docker.sock - command: make - entrypoint: /go/src/github.com/elastic/beats/metricbeat/docker-entrypoint.sh + + env_file: + - ${PWD}/module/apache/_meta/env + - ${PWD}/module/ceph/_meta/env + - ${PWD}/module/couchbase/_meta/env + - ${PWD}/module/haproxy/_meta/env + - ${PWD}/module/kafka/_meta/env + - ${PWD}/module/mongodb/_meta/env + - ${PWD}/module/mysql/_meta/env + - ${PWD}/module/nginx/_meta/env + - ${PWD}/module/php_fpm/_meta/env + - ${PWD}/module/postgresql/_meta/env + - ${PWD}/module/prometheus/_meta/env + - ${PWD}/module/redis/_meta/env + - ${PWD}/module/zookeeper/_meta/env # Modules apache: build: ${PWD}/module/apache/_meta ceph: - image: ceph/demo - hostname: ceph - environment: - - MON_IP=0.0.0.0 - - CEPH_PUBLIC_NETWORK=0.0.0.0/0 - expose: - - 5000 + build: ${PWD}/module/ceph/_meta couchbase: build: ${PWD}/module/couchbase/_meta + haproxy: + build: ${PWD}/module/haproxy/_meta + kafka: - image: spotify/kafka - expose: - - 9092 - - 2181 - environment: - - ADVERTISED_HOST=kafka + build: ${PWD}/module/kafka/_meta mongodb: - image: mongo:3.4 + build: ${PWD}/module/mongodb/_meta mysql: - image: mysql:5.7.12 - environment: - - MYSQL_ROOT_PASSWORD=test + build: ${PWD}/module/mysql/_meta nginx: build: ${PWD}/module/nginx/_meta - haproxy: - build: ${PWD}/module/haproxy/_meta - phpfpm: build: ${PWD}/module/php_fpm/_meta postgresql: - image: postgres:9.5.3 + build: ${PWD}/module/postgresql/_meta prometheus: - image: prom/prometheus - expose: - - 9090 + build: ${PWD}/module/prometheus/_meta redis: - image: redis:3.2.4-alpine + build: ${PWD}/module/redis/_meta zookeeper: - image: jplock/zookeeper:3.4.8 + build: ${PWD}/module/zookeeper/_meta diff --git a/metricbeat/docker-entrypoint.sh b/metricbeat/docker-entrypoint.sh index 0b0cee271ea..12a12b786d6 100755 --- a/metricbeat/docker-entrypoint.sh +++ b/metricbeat/docker-entrypoint.sh @@ -1,25 +1,7 @@ #!/usr/bin/env bash set -e -# Wait for. Params: host, port, service -waitFor() { - echo -n "Waiting for ${3}(${1}:${2}) to start." - for ((i=1; i<=90; i++)) do - if nc -vz ${1} ${2} 2>/dev/null; then - echo - echo "${3} is ready!" - return 0 - fi - - ((i++)) - echo -n '.' - sleep 1 - done - - echo - echo >&2 "${3} is not available" - echo >&2 "Address: ${1}:${2}" -} +source ./../libbeat/scripts/wait_for.sh # Main waitFor ${APACHE_HOST} ${APACHE_PORT} Apache diff --git a/metricbeat/module/apache/_meta/env b/metricbeat/module/apache/_meta/env new file mode 100644 index 00000000000..ffc83c4482e --- /dev/null +++ b/metricbeat/module/apache/_meta/env @@ -0,0 +1,2 @@ +APACHE_HOST=apache +APACHE_PORT=80 diff --git a/metricbeat/module/ceph/_meta/Dockerfile b/metricbeat/module/ceph/_meta/Dockerfile new file mode 100644 index 00000000000..84085c6121d --- /dev/null +++ b/metricbeat/module/ceph/_meta/Dockerfile @@ -0,0 +1,6 @@ +FROM ceph/demo + +ENV MON_IP 0.0.0.0 +ENV CEPH_PUBLIC_NETWORK 0.0.0.0/0 + +EXPOSE 5000 diff --git a/metricbeat/module/ceph/_meta/env b/metricbeat/module/ceph/_meta/env new file mode 100644 index 00000000000..27b7ce7dbfb --- /dev/null +++ b/metricbeat/module/ceph/_meta/env @@ -0,0 +1,2 @@ +CEPH_HOST=ceph +CEPH_PORT=5000 diff --git a/metricbeat/module/couchbase/_meta/env b/metricbeat/module/couchbase/_meta/env new file mode 100644 index 00000000000..38aa8e34778 --- /dev/null +++ b/metricbeat/module/couchbase/_meta/env @@ -0,0 +1,3 @@ +COUCHBASE_HOST=couchbase +COUCHBASE_PORT=8091 +COUCHBASE_DSN=http://Administrator:password@couchbase:8091 diff --git a/metricbeat/module/haproxy/_meta/env b/metricbeat/module/haproxy/_meta/env new file mode 100644 index 00000000000..34526c1be04 --- /dev/null +++ b/metricbeat/module/haproxy/_meta/env @@ -0,0 +1,2 @@ +HAPROXY_HOST=haproxy +HAPROXY_PORT=14567 diff --git a/metricbeat/module/kafka/_meta/Dockerfile b/metricbeat/module/kafka/_meta/Dockerfile new file mode 100644 index 00000000000..923a9d52a9e --- /dev/null +++ b/metricbeat/module/kafka/_meta/Dockerfile @@ -0,0 +1,5 @@ +FROM spotify/kafka + +EXPOSE 2181 9092 + +ENV ADVERTISED_HOST kafka diff --git a/metricbeat/module/kafka/_meta/env b/metricbeat/module/kafka/_meta/env new file mode 100644 index 00000000000..caf678712bf --- /dev/null +++ b/metricbeat/module/kafka/_meta/env @@ -0,0 +1,2 @@ +KAFKA_HOST=kafka +KAFKA_PORT=9092 diff --git a/metricbeat/module/mongodb/_meta/Dockerfile b/metricbeat/module/mongodb/_meta/Dockerfile new file mode 100644 index 00000000000..692f54c2fee --- /dev/null +++ b/metricbeat/module/mongodb/_meta/Dockerfile @@ -0,0 +1 @@ +FROM mongo:3.4 diff --git a/metricbeat/module/mongodb/_meta/env b/metricbeat/module/mongodb/_meta/env new file mode 100644 index 00000000000..a51807cbe81 --- /dev/null +++ b/metricbeat/module/mongodb/_meta/env @@ -0,0 +1,2 @@ +MONGODB_HOST=mongodb +MONGODB_PORT=27017 diff --git a/metricbeat/module/mysql/_meta/Dockerfile b/metricbeat/module/mysql/_meta/Dockerfile new file mode 100644 index 00000000000..5a2a14e1133 --- /dev/null +++ b/metricbeat/module/mysql/_meta/Dockerfile @@ -0,0 +1,3 @@ +FROM mysql:5.7.12 + +ENV MYSQL_ROOT_PASSWORD test diff --git a/metricbeat/module/mysql/_meta/env b/metricbeat/module/mysql/_meta/env new file mode 100644 index 00000000000..e6aa44eaf4a --- /dev/null +++ b/metricbeat/module/mysql/_meta/env @@ -0,0 +1,3 @@ +MYSQL_DSN=root:test@tcp(mysql:3306)/ +MYSQL_HOST=mysql +MYSQL_PORT=3306 diff --git a/metricbeat/module/nginx/_meta/env b/metricbeat/module/nginx/_meta/env new file mode 100644 index 00000000000..693e4c78989 --- /dev/null +++ b/metricbeat/module/nginx/_meta/env @@ -0,0 +1,2 @@ +NGINX_HOST=nginx +NGINX_PORT=80 diff --git a/metricbeat/module/php_fpm/_meta/env b/metricbeat/module/php_fpm/_meta/env new file mode 100644 index 00000000000..3437fa5b8c4 --- /dev/null +++ b/metricbeat/module/php_fpm/_meta/env @@ -0,0 +1,2 @@ +PHPFPM_HOST=phpfpm +PHPFPM_PORT=81 diff --git a/metricbeat/module/postgresql/_meta/Dockerfile b/metricbeat/module/postgresql/_meta/Dockerfile new file mode 100644 index 00000000000..d778b0871a1 --- /dev/null +++ b/metricbeat/module/postgresql/_meta/Dockerfile @@ -0,0 +1 @@ +FROM postgres:9.5.3 diff --git a/metricbeat/module/postgresql/_meta/env b/metricbeat/module/postgresql/_meta/env new file mode 100644 index 00000000000..eef1c64138c --- /dev/null +++ b/metricbeat/module/postgresql/_meta/env @@ -0,0 +1,4 @@ +POSTGRESQL_DSN=postgres://postgresql:5432?sslmode=disable +POSTGRESQL_HOST=postgresql +POSTGRESQL_PORT=5432 +POSTGRESQL_USERNAME=postgres diff --git a/metricbeat/module/prometheus/_meta/Dockerfile b/metricbeat/module/prometheus/_meta/Dockerfile new file mode 100644 index 00000000000..0d1e3831331 --- /dev/null +++ b/metricbeat/module/prometheus/_meta/Dockerfile @@ -0,0 +1,3 @@ +FROM prom/prometheus + +EXPOSE 9090 diff --git a/metricbeat/module/prometheus/_meta/env b/metricbeat/module/prometheus/_meta/env new file mode 100644 index 00000000000..a77775f7c6f --- /dev/null +++ b/metricbeat/module/prometheus/_meta/env @@ -0,0 +1,2 @@ +PROMETHEUS_HOST=prometheus +PROMETHEUS_PORT=9090 diff --git a/metricbeat/module/redis/_meta/Dockerfile b/metricbeat/module/redis/_meta/Dockerfile new file mode 100644 index 00000000000..9b89f61238b --- /dev/null +++ b/metricbeat/module/redis/_meta/Dockerfile @@ -0,0 +1 @@ +FROM redis:3.2.4-alpine diff --git a/metricbeat/module/redis/_meta/env b/metricbeat/module/redis/_meta/env new file mode 100644 index 00000000000..82b68167bc6 --- /dev/null +++ b/metricbeat/module/redis/_meta/env @@ -0,0 +1,2 @@ +REDIS_HOST=redis +REDIS_PORT=6379 diff --git a/metricbeat/module/zookeeper/_meta/Dockerfile b/metricbeat/module/zookeeper/_meta/Dockerfile new file mode 100644 index 00000000000..37d3f30ef9f --- /dev/null +++ b/metricbeat/module/zookeeper/_meta/Dockerfile @@ -0,0 +1 @@ +FROM jplock/zookeeper:3.4.8 diff --git a/metricbeat/module/zookeeper/_meta/env b/metricbeat/module/zookeeper/_meta/env new file mode 100644 index 00000000000..6a063ecec28 --- /dev/null +++ b/metricbeat/module/zookeeper/_meta/env @@ -0,0 +1,2 @@ +ZOOKEEPER_HOST=zookeeper +ZOOKEEPER_PORT=2181 From 21d875ff8ffba384f3ca3893b219a20c2844062a Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Mon, 6 Feb 2017 17:21:19 +0100 Subject: [PATCH 62/78] Rename reload config options according to #3430 (#3532) * Update docs * Update config file See https://github.com/elastic/beats/issues/3430 --- filebeat/config/config.go | 2 +- .../configuration/reload-configuration.asciidoc | 6 +++--- filebeat/tests/system/config/filebeat.yml.j2 | 6 +++--- libbeat/cfgfile/reload.go | 14 ++++++++++---- metricbeat/_meta/common.full.yml | 6 +++--- metricbeat/beater/config.go | 2 +- .../configuration/reload-configuration.asciidoc | 6 +++--- metricbeat/metricbeat.full.yml | 6 +++--- metricbeat/tests/system/config/metricbeat.yml.j2 | 6 +++--- 9 files changed, 30 insertions(+), 24 deletions(-) diff --git a/filebeat/config/config.go b/filebeat/config/config.go index 744c8094faa..9ca45685599 100644 --- a/filebeat/config/config.go +++ b/filebeat/config/config.go @@ -26,7 +26,7 @@ type Config struct { ConfigDir string `config:"config_dir"` ShutdownTimeout time.Duration `config:"shutdown_timeout"` Modules []*common.Config `config:"modules"` - ProspectorReload *common.Config `config:"reload.prospectors"` + ProspectorReload *common.Config `config:"config.prospectors"` } var ( diff --git a/filebeat/docs/reference/configuration/reload-configuration.asciidoc b/filebeat/docs/reference/configuration/reload-configuration.asciidoc index 06df9d6b0b9..87b99aa90b8 100644 --- a/filebeat/docs/reference/configuration/reload-configuration.asciidoc +++ b/filebeat/docs/reference/configuration/reload-configuration.asciidoc @@ -11,10 +11,10 @@ The configuration in the main filebeat.yml config file looks as following: [source,yaml] ------------------------------------------------------------------------------ -filebeat.reload.prospectors: - enabled: true +filebeat.config.prospectors: path: configs/*.yml - period: 10s + reload.enabled: true + reload.period: 10s ------------------------------------------------------------------------------ A path with a glob must be defined on which files should be checked for changes. A period is set on how often diff --git a/filebeat/tests/system/config/filebeat.yml.j2 b/filebeat/tests/system/config/filebeat.yml.j2 index 63639b3209d..ce19ea2a534 100644 --- a/filebeat/tests/system/config/filebeat.yml.j2 +++ b/filebeat/tests/system/config/filebeat.yml.j2 @@ -80,10 +80,10 @@ filebeat.registry_file: {{ beat.working_dir + '/' }}{{ registryFile|default("reg filebeat.publish_async: {{publish_async}} {% if reload -%} -filebeat.reload.prospectors: +filebeat.config.prospectors: path: {{ reload_path }} - period: 1s - enabled: true + reload.period: 1s + reload.enabled: true {% endif -%} #================================ General ===================================== diff --git a/libbeat/cfgfile/reload.go b/libbeat/cfgfile/reload.go index 1e8f6471710..2618858b89f 100644 --- a/libbeat/cfgfile/reload.go +++ b/libbeat/cfgfile/reload.go @@ -13,8 +13,10 @@ import ( var ( DefaultReloadConfig = ReloadConfig{ - Period: 10 * time.Second, - Enabled: false, + Reload: Reload{ + Period: 10 * time.Second, + Enabled: false, + }, } debugf = logp.MakeDebug("cfgfile") @@ -27,7 +29,11 @@ var ( type ReloadConfig struct { // If path is a relative path, it is relative to the ${path.config} - Path string `config:"path"` + Path string `config:"path"` + Reload Reload `config:"reload"` +} + +type Reload struct { Period time.Duration `config:"period"` Enabled bool `config:"enabled"` } @@ -86,7 +92,7 @@ func (rl *Reloader) Run(runnerFactory RunnerFactory) { case <-rl.done: logp.Info("Dynamic config reloader stopped") return - case <-time.After(rl.config.Period): + case <-time.After(rl.config.Reload.Period): debugf("Scan for new config files") configReloads.Add(1) diff --git a/metricbeat/_meta/common.full.yml b/metricbeat/_meta/common.full.yml index 1a7199d5c10..9a2e1bde868 100644 --- a/metricbeat/_meta/common.full.yml +++ b/metricbeat/_meta/common.full.yml @@ -11,13 +11,13 @@ # Config reloading allows to dynamically load modules. Each file which is # monitored must contain one or multiple modules as a list. -metricbeat.modules.reload: +metricbeat.config.modules: # Glob pattern for configuration reloading path: ${path.config}/conf.d/*.yml # Period on which files under path should be checked for chagnes - period: 10s + reload.period: 10s # Set to true to enable config reloading - enabled: false + reload.enabled: false diff --git a/metricbeat/beater/config.go b/metricbeat/beater/config.go index c49b255999a..7a2acbb9458 100644 --- a/metricbeat/beater/config.go +++ b/metricbeat/beater/config.go @@ -6,5 +6,5 @@ import "github.com/elastic/beats/libbeat/common" type Config struct { // Modules is a list of module specific configuration data. Modules []*common.Config `config:"modules"` - ReloadModules *common.Config `config:"reload.modules"` + ReloadModules *common.Config `config:"config.modules"` } diff --git a/metricbeat/docs/reference/configuration/reload-configuration.asciidoc b/metricbeat/docs/reference/configuration/reload-configuration.asciidoc index 292474d5b1f..362fdff94bd 100644 --- a/metricbeat/docs/reference/configuration/reload-configuration.asciidoc +++ b/metricbeat/docs/reference/configuration/reload-configuration.asciidoc @@ -12,10 +12,10 @@ The configuration in the main metricbeat.yml config file looks as following: [source,yaml] ------------------------------------------------------------------------------ -metricbeat.reload.modules: - enabled: true +metricbeat.config.modules: path: configs/*.yml - period: 10s + reload.enabled: true + reload.period: 10s ------------------------------------------------------------------------------ A path with a glob must be defined on which files should be checked for changes. A period is set on how often diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 4a4e17bd1c3..c50dcaa4ad3 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -11,16 +11,16 @@ # Config reloading allows to dynamically load modules. Each file which is # monitored must contain one or multiple modules as a list. -metricbeat.modules.reload: +metricbeat.config.modules: # Glob pattern for configuration reloading path: ${path.config}/conf.d/*.yml # Period on which files under path should be checked for chagnes - period: 10s + reload.period: 10s # Set to true to enable config reloading - enabled: false + reload.enabled: false #========================== Modules configuration ============================ metricbeat.modules: diff --git a/metricbeat/tests/system/config/metricbeat.yml.j2 b/metricbeat/tests/system/config/metricbeat.yml.j2 index 7ff28cf4f05..da5a6b15609 100644 --- a/metricbeat/tests/system/config/metricbeat.yml.j2 +++ b/metricbeat/tests/system/config/metricbeat.yml.j2 @@ -81,10 +81,10 @@ metricbeat.modules: {%- endfor %} {% if reload -%} -metricbeat.reload.modules: +metricbeat.config.modules: path: {{ reload_path }} - period: 1s - enabled: true + reload.period: 1s + reload.enabled: true {% endif -%} #================================ General ===================================== From f13e66e1ff242db4018c216eae018510f591db91 Mon Sep 17 00:00:00 2001 From: Toby McLaughlin Date: Tue, 7 Feb 2017 17:56:31 +1100 Subject: [PATCH 63/78] Fix docs, logs for setting UID/GID in Packetbeat (#3543) * Example for 'packetbeat.runopts' is now valid YAML. * Log output now correctly dereferences UID, GID variables. --- libbeat/common/droppriv/droppriv_unix.go | 2 +- packetbeat/docs/reference/configuration/runconfig.asciidoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libbeat/common/droppriv/droppriv_unix.go b/libbeat/common/droppriv/droppriv_unix.go index 67548ea60f4..d9516afd1ed 100644 --- a/libbeat/common/droppriv/droppriv_unix.go +++ b/libbeat/common/droppriv/droppriv_unix.go @@ -27,7 +27,7 @@ func DropPrivileges(config RunOptions) error { return errors.New("GID must be specified for dropping privileges") } - logp.Info("Switching to user: %d.%d", config.UID, config.GID) + logp.Info("Switching to user: %d.%d", *config.UID, *config.GID) if err = syscall.Setgid(*config.GID); err != nil { return fmt.Errorf("setgid: %s", err.Error()) diff --git a/packetbeat/docs/reference/configuration/runconfig.asciidoc b/packetbeat/docs/reference/configuration/runconfig.asciidoc index 39b1a990a30..9f5f2dfe639 100644 --- a/packetbeat/docs/reference/configuration/runconfig.asciidoc +++ b/packetbeat/docs/reference/configuration/runconfig.asciidoc @@ -16,8 +16,8 @@ Example configuration for the `runoptions` section of the +{beatname_lc}.yml+ co [source,yaml] ------------------------------------------------------------------------------ packetbeat.runoptions: - uid=501 - gid=501 + uid: 501 + gid: 501 ------------------------------------------------------------------------------ The `runoptions` configuration is supported on Linux only. From c1cfd7054abaee7cd970c34d66b736c5ba6cb444 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 7 Feb 2017 08:09:11 +0100 Subject: [PATCH 64/78] Update experimental notices for 5.3 (#3525) Configuration reloading -> beta[] Filebeat symlinks -> GA Filebeat harvester_limit -> GA Filebeat publish_async -> experimental & deprecated Using environment variables in the Configuration -> GA Metricbeat ceph -> beta[] Metricbeat couchbase -> beta[] Metricbeat docker -> beta[] Metricbeat haproxy -> GA Metricbeat kafka -> beta[] Metricbeat php_fpm -> beta[] Metricbeat prometheus -> beta[] This also cleans up the CHANGELOG. --- CHANGELOG.asciidoc | 157 ++++++++++++------ filebeat/_meta/common.full.p2.yml | 4 +- filebeat/crawler/crawler.go | 2 +- .../configuration/filebeat-options.asciidoc | 5 +- .../reload-configuration.asciidoc | 2 +- filebeat/filebeat.full.yml | 4 +- filebeat/publisher/publisher.go | 2 +- libbeat/docs/shared-env-vars.asciidoc | 2 - metricbeat/docs/fields.asciidoc | 22 +-- metricbeat/docs/modules/ceph.asciidoc | 2 + metricbeat/docs/modules/couchbase.asciidoc | 2 +- metricbeat/docs/modules/docker.asciidoc | 2 +- metricbeat/docs/modules/kafka.asciidoc | 2 +- metricbeat/docs/modules/php_fpm.asciidoc | 2 + metricbeat/docs/modules/prometheus.asciidoc | 2 + metricbeat/module/ceph/_meta/docs.asciidoc | 2 + metricbeat/module/ceph/_meta/fields.yml | 3 +- .../ceph/cluster_health/cluster_health.go | 2 +- .../ceph/monitor_health/monitor_health.go | 2 +- .../module/couchbase/_meta/docs.asciidoc | 2 +- metricbeat/module/couchbase/_meta/fields.yml | 2 +- .../module/couchbase/bucket/_meta/fields.yml | 2 +- metricbeat/module/couchbase/bucket/bucket.go | 2 +- .../module/couchbase/cluster/_meta/fields.yml | 2 +- .../module/couchbase/cluster/cluster.go | 2 +- .../module/couchbase/node/_meta/fields.yml | 2 +- metricbeat/module/couchbase/node/node.go | 2 +- metricbeat/module/docker/_meta/docs.asciidoc | 2 +- metricbeat/module/docker/_meta/fields.yml | 2 +- .../module/docker/container/container.go | 2 +- metricbeat/module/docker/cpu/cpu.go | 2 +- metricbeat/module/docker/diskio/diskio.go | 2 +- .../module/docker/healthcheck/healthcheck.go | 2 +- metricbeat/module/docker/image/image.go | 2 +- .../module/docker/info/_meta/fields.yml | 2 +- metricbeat/module/docker/info/info.go | 2 +- metricbeat/module/docker/memory/memory.go | 2 +- metricbeat/module/docker/network/network.go | 2 +- metricbeat/module/haproxy/_meta/fields.yml | 2 - metricbeat/module/haproxy/info/info.go | 2 - metricbeat/module/haproxy/stat/stat.go | 2 - metricbeat/module/kafka/_meta/docs.asciidoc | 2 +- metricbeat/module/kafka/_meta/fields.yml | 2 +- .../kafka/consumergroup/consumergroup.go | 2 +- .../module/kafka/partition/partition.go | 2 +- metricbeat/module/php_fpm/_meta/docs.asciidoc | 2 + metricbeat/module/php_fpm/_meta/fields.yml | 4 +- metricbeat/module/php_fpm/pool/pool.go | 2 +- .../module/prometheus/_meta/docs.asciidoc | 2 + metricbeat/module/prometheus/_meta/fields.yml | 4 +- .../module/prometheus/collector/collector.go | 2 +- metricbeat/module/prometheus/stats/stats.go | 2 +- metricbeat/tests/system/test_docker.py | 16 +- metricbeat/tests/system/test_haproxy.py | 4 +- metricbeat/tests/system/test_phpfpm.py | 3 +- metricbeat/tests/system/test_prometheus.py | 3 +- 56 files changed, 183 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 3453fc6086a..1e0f4936bfd 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -16,6 +16,10 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Change beat generator. Use `$GOPATH/src/github.com/elastic/beats/script/generate.py` to generate a beat. {pull}3452[3452] +*Filebeat* + +*Heartbeat* + *Metricbeat* - Linux cgroup metrics are now enabled by default for the system process metricset. The configuration option for the feature was renamed from @@ -23,10 +27,6 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Packetbeat* -*Topbeat* - -*Filebeat* - *Winlogbeat* @@ -34,105 +34,154 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Affecting all Beats* -- Add `_id`, `_type`, `_index` and `_score` fields in the generated index pattern. {pull}3282}[3282] +- Add `_id`, `_type`, `_index` and `_score` fields in the generated index pattern. {pull}3282[3282] + +*Filebeat* + +*Heartbeat* *Metricbeat* -- Fix service times-out at startup. {pull}3056[3056] -- Kafka module case sensitive host name matching. {pull}3193[3193] -- Fix interface conversion panic in couchbase module {pull}3272[3272] -- Fix overwriting explicit empty config sections {issue}2918[2918] - Fix go routine leak in docker module. {pull}3492[3492] *Packetbeat* -- Fix issue where some Cassandra visualizations were showing data from all protocols. {issue}3314[3314] - -*Topbeat* - -*Filebeat* -- Fix registry cleanup issue when files falling under ignore_older after restart. {issue}2818[2818] -- Fix registry migration issue from old states were files were only harvested after second restart. {pull}3322[3322] -- Fix alignment issue were Filebeat compiled with Go 1.7.4 was crashing on 32 bits system. {issue}3273[3273] *Winlogbeat* -- Fix for "The array bounds are invalid" error when reading large events. {issue}3076[3076] + ==== Added *Affecting all Beats* -- Add support for passing list and dictionary settings via -E flag. -- Support for parsing list and dictionary setting from environment variables. -- Added new flags to import_dashboards (-cacert, -cert, -key, -insecure). {pull}3139[3139] {pull}3163[3163] -- The limit for the number of fields is increased via the mapping template. {pull}3275[3275] -- Updated to Go 1.7.4. {pull}3277[3277] -- Added a NOTICE file containing the notices and licenses of the dependencies. {pull}3334[3334]. + - Files created by Beats (logs, registry, file output) will have 0600 permissions. {pull}3387[3387]. - RPM/deb packages will now install the config file with 0600 permissions. {pull}3382[3382] - Add the option to pass custom HTTP headers to the Elasticsearch output. {pull}3400[3400] - Unify `regexp` and `contains` conditionals, for both to support array of strings and convert numbers to strings if required. {pull}3469[3469] - Add the option to load the sample dashboards during the Beat startup phase. {pull}3506[3506] - Disabled date detection in Elasticsearch index templates. Date fields must be explicitly defined in index templates. {pull}3528[3528] +- Using environment variables in the configuration file is now GA, instead of experimental. {pull}3525[3525] + +*Filebeat* + +- Add the `pipeline` config option at the prospector level, for configuring the Ingest Node pipeline ID. {pull}3433[3433] +- Update regular expressions used for matching file names or lines (multiline, include/exclude functionality) to new matchers improving performance of simple string matches. {pull}3469[3469] +- The `symlinks` and `harverster_limit` settings are now GA, instead of experimental. {pull}3525[3525] + +*Heartbeat* *Metricbeat* -- Add experimental filebeat metricset in the beats module. {pull}2297[2297] -- Add experimental libbeat metricset in the beats module. {pull}2339[2339] -- Add experimental docker module. Provided by Ingensi and @douaejeouit based on dockbeat. -- Add username and password config options to the MongoDB module. {pull}2889[2889] -- Add username and password config options to the PostgreSQL module. {pull}2889[2890] -- Add system core metricset for Windows. {pull}2883[2883] -- Add a sample Redis Kibana dashboard. {pull}2916[2916] -- Add support for MongoDB 3.4 and WiredTiger metrics. {pull}2999[2999] -- Add experimental kafka module with partition metricset. {pull}2969[2969] -- Add raw config option for mysql/status metricset. {pull}3001[3001] - Add experimental dbstats metricset to MongoDB module. {pull}3228[3228] - Use persistent, direct connections to the configured nodes for MongoDB module. {pull}3228[3228] -- Kafka module broker matching enhancements. {pull}3129[3129] -- Add a couchbase module with metricsets for node, cluster and bucket. {pull}3081[3081] -- Export number of cores for cpu module. {pull}3192[3192] -- Experimental Prometheus module. {pull}3202[3202] -- Add system socket module that reports all TCP sockets. {pull}3246[3246] -- Kafka consumer groups metricset. {pull}3240[3240] - Add dynamic configuration reloading for modules. {pull}3281[3281] - Add docker health metricset {pull}3357[3357] - Add docker image metricset {pull}3467[3467] - System module uses new matchers for white-listing processes. {pull}3469[3469] -- Add CEPH module with health metricset. {pull}3311[3311] -- Add php_fpm module with pool metricset. {pull}3415[3415] -- Add cluster_disk and pool_disk metricsets to CEPH module. {pull}3499[3499] +- Add Beta CEPH module with health metricset. {pull}3311[3311] +- Add Beta php_fpm module with pool metricset. {pull}3415[3415] +- The Docker, Kafka, and Prometheus modules are now Beta, instead of experimental. {pull}3525[3525] +- The HAProxy module is now GA, instead of experimental. {pull}3525[3525] *Packetbeat* -*Topbeat* +*Winlogbeat* + +==== Deprecated + +*Affecting all Beats* + +- Usage of field _type is deprecated. It should not be used in queries or dashboards. {pull}3409[3409] *Filebeat* -- Add enabled config option to prospectors. {pull}3157[3157] -- Add target option for decoded_json_field. {pull}3169[3169] -- Add the `pipeline` config option at the prospector level, for configuring the Ingest Node pipeline ID. {pull}3433[3433] -- Update regular expressions used for matching file names or lines (multiline, include/exclude functionality) to new matchers improving performance of simple string matches. {pull}3469[3469] + +- The experimental `publish_async` option is now deprecated and is planned to be removed in 6.0. {pull}3525[3525] + +*Heartbeat* + +*Metricbeat* + +*Packetbeat* *Winlogbeat* -- Reduced amount of memory allocated while reading event log records. {pull}3113[3113] {pull}3118[3113] +//////////////////////////////////////////////////////////// -==== Deprecated +[[release-notes-5.2.0]] +=== Beats version 5.2.0 +https://github.com/elastic/beats/compare/v5.1.2...v5.2.0[View commits] + +==== Bugfixes *Affecting all Beats* -- Usage of field _type is deprecated. It should not be used in queries or dashboards. {pull}3409[3409] + +- Fix overwriting explicit empty config sections. {issue}2918[2918] + +*Filebeat* + +- Fix alignment issue were Filebeat compiled with Go 1.7.4 was crashing on 32 bits system. {issue}3273[3273] *Metricbeat* +- Fix service times-out at startup. {pull}3056[3056] +- Kafka module case sensitive host name matching. {pull}3193[3193] +- Fix interface conversion panic in couchbase module {pull}3272[3272] + *Packetbeat* -*Topbeat* -- Fix error on importing dashboards due to colons in the Caassandra dashboard. {issue}3140[3140] +- Fix issue where some Cassandra visualizations were showing data from all protocols. {issue}3314[3314] + +==== Added + +*Affecting all Beats* + +- Add support for passing list and dictionary settings via -E flag. +- Support for parsing list and dictionary setting from environment variables. +- Added new flags to import_dashboards (-cacert, -cert, -key, -insecure). {pull}3139[3139] {pull}3163[3163] +- The limit for the number of fields is increased via the mapping template. {pull}3275[3275] +- Updated to Go 1.7.4. {pull}3277[3277] +- Added a NOTICE file containing the notices and licenses of the dependencies. {pull}3334[3334]. + +*Heartbeat* + +- First release, containing monitors for ICMP, TCP, and HTTP. *Filebeat* +- Add enabled config option to prospectors. {pull}3157[3157] +- Add target option for decoded_json_field. {pull}3169[3169] + +*Metricbeat* + +- Kafka module broker matching enhancements. {pull}3129[3129] +- Add a couchbase module with metricsets for node, cluster and bucket. {pull}3081[3081] +- Export number of cores for CPU module. {pull}3192[3192] +- Experimental Prometheus module. {pull}3202[3202] +- Add system socket module that reports all TCP sockets. {pull}3246[3246] +- Kafka consumer groups metricset. {pull}3240[3240] + *Winlogbeat* -//////////////////////////////////////////////////////////// +- Reduced amount of memory allocated while reading event log records. {pull}3113[3113] {pull}3118[3118] + +[[release-notes-5.1.2]] +=== Beats version 5.1.2 +https://github.com/elastic/beats/compare/v5.1.1...v5.1.2[View commits] + +==== Bugfixes + +*Filebeat* + +- Fix registry migration issue from old states where files were only harvested after second restart. {pull}3322[3322] + +*Packetbeat* + +- Fix error on importing dashboards due to colons in the Cassandra dashboard. {issue}3140[3140] +- Fix error on importing dashboards due to the wrong type for the geo_point fields. {pull}3147[3147] + +*Winlogbeat* + +- Fix for "The array bounds are invalid" error when reading large events. {issue}3076[3076] [[release-notes-5.1.1]] === Beats version 5.1.1 diff --git a/filebeat/_meta/common.full.p2.yml b/filebeat/_meta/common.full.p2.yml index f766b74dcc4..c2264822eee 100644 --- a/filebeat/_meta/common.full.p2.yml +++ b/filebeat/_meta/common.full.p2.yml @@ -138,7 +138,7 @@ filebeat.prospectors: # overwrites the pipeline option from the Elasticsearch output. #pipeline: - # Experimental: If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the + # If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the # original for harvesting but will report the symlink name as source. #symlinks: false @@ -160,7 +160,7 @@ filebeat.prospectors: # The backoff value will be multiplied each time with the backoff_factor until max_backoff is reached #backoff_factor: 2 - # Experimental: Max number of harvesters that are started in parallel. + # Max number of harvesters that are started in parallel. # Default is 0 which means unlimited #harvester_limit: 0 diff --git a/filebeat/crawler/crawler.go b/filebeat/crawler/crawler.go index 8cc710e7d59..cf56423db4f 100644 --- a/filebeat/crawler/crawler.go +++ b/filebeat/crawler/crawler.go @@ -44,7 +44,7 @@ func (c *Crawler) Start(r *registrar.Registrar, reloaderConfig *common.Config) e } if reloaderConfig.Enabled() { - logp.Warn("EXPERIMENTAL feature dynamic configuration reloading is enabled.") + logp.Warn("BETA feature dynamic configuration reloading is enabled.") c.reloader = cfgfile.NewReloader(reloaderConfig) factory := prospector.NewFactory(c.out, r) diff --git a/filebeat/docs/reference/configuration/filebeat-options.asciidoc b/filebeat/docs/reference/configuration/filebeat-options.asciidoc index ec9e66fa064..9e7eab88c47 100644 --- a/filebeat/docs/reference/configuration/filebeat-options.asciidoc +++ b/filebeat/docs/reference/configuration/filebeat-options.asciidoc @@ -415,8 +415,6 @@ NOTE: The pipeline ID can also be configured in the Elasticsearch output, but th ===== symlinks -experimental[] - The `symlinks` option allows Filebeat to harvest symlinks in addition to regular files. When harvesting symlinks, Filebeat opens and reads the original file even though it reports the path of the symlink. When you configure a symlink for harvesting, make sure the original path is excluded. If a single prospector is configured to harvest both the symlink and the original file, the prospector will detect the problem and only process the first file it finds. However, if two different prospectors are configured (one to read the symlink and the other the original path), both paths will be harvested, causing Filebeat to send duplicate data and the prospectors to overwrite each other's state. @@ -457,8 +455,6 @@ lines. The `backoff` value will be multiplied each time with the `backoff_factor [[harvester-limit]] ===== harvester_limit -experimental[] - The `harvester_limit` option limits the number of harvesters that are started in parallel for one prospector. This directly relates to the maximum number of file handlers that are opened. The default for `harvester_limit` is 0, which means there is no limit. This configuration is useful if the number of files to be harvested exceeds the open file handler limit of the operating system. @@ -498,6 +494,7 @@ See <> for more information about how this setting affects load ===== publish_async experimental[] +deprecated[5.3.0] If enabled, the publisher pipeline in Filebeat operates in async mode preparing a new batch of lines while waiting for ACK. This option can improve load-balancing diff --git a/filebeat/docs/reference/configuration/reload-configuration.asciidoc b/filebeat/docs/reference/configuration/reload-configuration.asciidoc index 87b99aa90b8..1f6842f5425 100644 --- a/filebeat/docs/reference/configuration/reload-configuration.asciidoc +++ b/filebeat/docs/reference/configuration/reload-configuration.asciidoc @@ -1,7 +1,7 @@ [[filebeat-configuration-reloading]] === Reload Configuration -experimental[] +beta[] Reload configuration allows to dynamically reload prospector configuration files. A glob can be defined which should be watched for prospector configuration changes. New prospectors will be started / stopped accordingly. This is especially useful in diff --git a/filebeat/filebeat.full.yml b/filebeat/filebeat.full.yml index f61a8f24be9..fd68d4590da 100644 --- a/filebeat/filebeat.full.yml +++ b/filebeat/filebeat.full.yml @@ -254,7 +254,7 @@ filebeat.prospectors: # overwrites the pipeline option from the Elasticsearch output. #pipeline: - # Experimental: If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the + # If symlinks is enabled, symlinks are opened and harvested. The harvester is openening the # original for harvesting but will report the symlink name as source. #symlinks: false @@ -276,7 +276,7 @@ filebeat.prospectors: # The backoff value will be multiplied each time with the backoff_factor until max_backoff is reached #backoff_factor: 2 - # Experimental: Max number of harvesters that are started in parallel. + # Max number of harvesters that are started in parallel. # Default is 0 which means unlimited #harvester_limit: 0 diff --git a/filebeat/publisher/publisher.go b/filebeat/publisher/publisher.go index 914208b9679..e77321d3b80 100644 --- a/filebeat/publisher/publisher.go +++ b/filebeat/publisher/publisher.go @@ -34,7 +34,7 @@ func New( pub publisher.Publisher, ) LogPublisher { if async { - logp.Warn("Using publish_async is experimental!") + logp.Warn("publish_async is experimental and will be removed in a future version!") return newAsyncLogPublisher(in, out, pub) } return newSyncLogPublisher(in, out, pub) diff --git a/libbeat/docs/shared-env-vars.asciidoc b/libbeat/docs/shared-env-vars.asciidoc index c61d4663250..ae55ac7f6f7 100644 --- a/libbeat/docs/shared-env-vars.asciidoc +++ b/libbeat/docs/shared-env-vars.asciidoc @@ -12,8 +12,6 @@ [[using-environ-vars]] == Using Environment Variables in the Configuration -experimental[] - You can use environment variable references in the +{beatname_lc}.yml+ file to set values that need to be configurable during deployment. To do this, use: diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index c4f1040b32c..89ce3efe740 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -416,7 +416,8 @@ Contains user configurable fields. [[exported-fields-ceph]] == ceph Fields -experimental[] ceph Module +beta[] +ceph Module @@ -780,7 +781,7 @@ The document type. Always set to "metricsets". [[exported-fields-couchbase]] == couchbase Fields -experimental[] +beta[] couchbase Module @@ -795,7 +796,7 @@ couchbase Module [float] == bucket Fields -experimental[] +beta[] Couchbase bucket metrics @@ -893,7 +894,7 @@ Number of items associated with the bucket [float] == cluster Fields -experimental[] +beta[] Couchbase Cluster metrics @@ -1045,7 +1046,7 @@ Ram used by the data in the cluster (bytes) [float] == node Fields -experimental[] +beta[] Couchbase node metrics @@ -1243,7 +1244,7 @@ Number of items/documents that are replicas [[exported-fields-docker]] == Docker Fields -experimental[] +beta[] Docker stats collected from Docker. @@ -1559,7 +1560,7 @@ Total size of the all cached images associated to the current image. [float] == info Fields -experimental[] +beta[] Info metrics based on https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/display-system-wide-information. @@ -1804,7 +1805,6 @@ Total number of outgoing packets. [[exported-fields-haproxy]] == HAProxy Fields -experimental[] HAProxy Module @@ -2779,7 +2779,7 @@ The average queue time in ms over the last 1024 requests. == kafka Fields kafka Module -experimental[] +beta[] @@ -4190,8 +4190,8 @@ The current number of idle client connections waiting for a request. [[exported-fields-php_fpm]] == php_fpm Fields +beta[] PHP-FPM server status metrics collected from PHP-FPM. -experimental[] @@ -4675,8 +4675,8 @@ Time at which these statistics were last reset. [[exported-fields-prometheus]] == Prometheus Fields +beta[] Prometheus Module -experimental[] diff --git a/metricbeat/docs/modules/ceph.asciidoc b/metricbeat/docs/modules/ceph.asciidoc index bd4963baee8..fcbc1ac9a48 100644 --- a/metricbeat/docs/modules/ceph.asciidoc +++ b/metricbeat/docs/modules/ceph.asciidoc @@ -5,6 +5,8 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-ceph]] == ceph Module +beta[] + This is the ceph Module. Metrics are collected submitting HTTP GET requests to ceph-rest-api. Reference: http://docs.ceph.com/docs/master/man/8/ceph-rest-api/ diff --git a/metricbeat/docs/modules/couchbase.asciidoc b/metricbeat/docs/modules/couchbase.asciidoc index 5a174221660..936590eb62e 100644 --- a/metricbeat/docs/modules/couchbase.asciidoc +++ b/metricbeat/docs/modules/couchbase.asciidoc @@ -5,7 +5,7 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-couchbase]] == couchbase Module -experimental[] +beta[] This is the couchbase Module. diff --git a/metricbeat/docs/modules/docker.asciidoc b/metricbeat/docs/modules/docker.asciidoc index 2d11ff9b87b..fda384ef366 100644 --- a/metricbeat/docs/modules/docker.asciidoc +++ b/metricbeat/docs/modules/docker.asciidoc @@ -5,7 +5,7 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-docker]] == Docker Module -experimental[] +beta[] This module fetches metrics from https://www.docker.com/[Docker] containers. diff --git a/metricbeat/docs/modules/kafka.asciidoc b/metricbeat/docs/modules/kafka.asciidoc index e606f00bae9..f2a0a9752db 100644 --- a/metricbeat/docs/modules/kafka.asciidoc +++ b/metricbeat/docs/modules/kafka.asciidoc @@ -5,7 +5,7 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-kafka]] == kafka Module -experimental[] +beta[] This is the kafka Module. diff --git a/metricbeat/docs/modules/php_fpm.asciidoc b/metricbeat/docs/modules/php_fpm.asciidoc index 2440b1935f0..dbdebeebe56 100644 --- a/metricbeat/docs/modules/php_fpm.asciidoc +++ b/metricbeat/docs/modules/php_fpm.asciidoc @@ -5,6 +5,8 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-php_fpm]] == PHP-FPM Module +beta[] + This module periodically fetches metrics from https://php-fpm.org[PHP-FPM] servers. diff --git a/metricbeat/docs/modules/prometheus.asciidoc b/metricbeat/docs/modules/prometheus.asciidoc index 2ec6eefa8b5..ce5422b6855 100644 --- a/metricbeat/docs/modules/prometheus.asciidoc +++ b/metricbeat/docs/modules/prometheus.asciidoc @@ -5,6 +5,8 @@ This file is generated! See scripts/docs_collector.py [[metricbeat-module-prometheus]] == prometheus Module +beta[] + This is the prometheus Module. diff --git a/metricbeat/module/ceph/_meta/docs.asciidoc b/metricbeat/module/ceph/_meta/docs.asciidoc index 20015a2ad96..7cfa7e0014f 100644 --- a/metricbeat/module/ceph/_meta/docs.asciidoc +++ b/metricbeat/module/ceph/_meta/docs.asciidoc @@ -1,4 +1,6 @@ == ceph Module +beta[] + This is the ceph Module. Metrics are collected submitting HTTP GET requests to ceph-rest-api. Reference: http://docs.ceph.com/docs/master/man/8/ceph-rest-api/ diff --git a/metricbeat/module/ceph/_meta/fields.yml b/metricbeat/module/ceph/_meta/fields.yml index 71ee34a9755..8b4800963b4 100644 --- a/metricbeat/module/ceph/_meta/fields.yml +++ b/metricbeat/module/ceph/_meta/fields.yml @@ -1,7 +1,8 @@ - key: ceph title: "ceph" description: > - experimental[] + beta[] + ceph Module short_config: false fields: diff --git a/metricbeat/module/ceph/cluster_health/cluster_health.go b/metricbeat/module/ceph/cluster_health/cluster_health.go index 064e78bfdfc..07622b2c4dd 100644 --- a/metricbeat/module/ceph/cluster_health/cluster_health.go +++ b/metricbeat/module/ceph/cluster_health/cluster_health.go @@ -32,7 +32,7 @@ type MetricSet struct { } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The ceph cluster_health metricset is experimental") + logp.Warn("BETA: The ceph cluster_health metricset is beta") http := helper.NewHTTP(base) http.SetHeader("Accept", "application/json") diff --git a/metricbeat/module/ceph/monitor_health/monitor_health.go b/metricbeat/module/ceph/monitor_health/monitor_health.go index 8bcb68e515a..c00de44a364 100644 --- a/metricbeat/module/ceph/monitor_health/monitor_health.go +++ b/metricbeat/module/ceph/monitor_health/monitor_health.go @@ -34,7 +34,7 @@ type MetricSet struct { } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The ceph monitor_health metricset is experimental") + logp.Warn("BETA: The ceph monitor_health metricset is beta") http := helper.NewHTTP(base) http.SetHeader("Accept", "application/json") diff --git a/metricbeat/module/couchbase/_meta/docs.asciidoc b/metricbeat/module/couchbase/_meta/docs.asciidoc index a5be1c73519..c24ad610a59 100644 --- a/metricbeat/module/couchbase/_meta/docs.asciidoc +++ b/metricbeat/module/couchbase/_meta/docs.asciidoc @@ -1,6 +1,6 @@ == couchbase Module -experimental[] +beta[] This is the couchbase Module. diff --git a/metricbeat/module/couchbase/_meta/fields.yml b/metricbeat/module/couchbase/_meta/fields.yml index 18521c2b6a0..b6f5701bfb6 100644 --- a/metricbeat/module/couchbase/_meta/fields.yml +++ b/metricbeat/module/couchbase/_meta/fields.yml @@ -1,7 +1,7 @@ - key: couchbase title: "couchbase" description: > - experimental[] + beta[] couchbase Module short_config: false diff --git a/metricbeat/module/couchbase/bucket/_meta/fields.yml b/metricbeat/module/couchbase/bucket/_meta/fields.yml index 1f97e6381e1..5ddcc41a734 100644 --- a/metricbeat/module/couchbase/bucket/_meta/fields.yml +++ b/metricbeat/module/couchbase/bucket/_meta/fields.yml @@ -1,7 +1,7 @@ - name: bucket type: group description: > - experimental[] + beta[] Couchbase bucket metrics fields: diff --git a/metricbeat/module/couchbase/bucket/bucket.go b/metricbeat/module/couchbase/bucket/bucket.go index e0ac9d4af50..ed7c127ffe0 100644 --- a/metricbeat/module/couchbase/bucket/bucket.go +++ b/metricbeat/module/couchbase/bucket/bucket.go @@ -36,7 +36,7 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The couchbase bucket metricset is experimental") + logp.Warn("BETA: The couchbase bucket metricset is beta") return &MetricSet{ BaseMetricSet: base, diff --git a/metricbeat/module/couchbase/cluster/_meta/fields.yml b/metricbeat/module/couchbase/cluster/_meta/fields.yml index 8b54aba10a4..ca914a5cbb8 100644 --- a/metricbeat/module/couchbase/cluster/_meta/fields.yml +++ b/metricbeat/module/couchbase/cluster/_meta/fields.yml @@ -1,7 +1,7 @@ - name: cluster type: group description: > - experimental[] + beta[] Couchbase Cluster metrics fields: diff --git a/metricbeat/module/couchbase/cluster/cluster.go b/metricbeat/module/couchbase/cluster/cluster.go index 9de12620531..db80e98d0f3 100644 --- a/metricbeat/module/couchbase/cluster/cluster.go +++ b/metricbeat/module/couchbase/cluster/cluster.go @@ -36,7 +36,7 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The couchbase cluster metricset is experimental") + logp.Warn("BETA: The couchbase cluster metricset is beta") return &MetricSet{ BaseMetricSet: base, diff --git a/metricbeat/module/couchbase/node/_meta/fields.yml b/metricbeat/module/couchbase/node/_meta/fields.yml index b0e7cd58869..77ba5719d5c 100644 --- a/metricbeat/module/couchbase/node/_meta/fields.yml +++ b/metricbeat/module/couchbase/node/_meta/fields.yml @@ -1,7 +1,7 @@ - name: node type: group description: > - experimental[] + beta[] Couchbase node metrics fields: diff --git a/metricbeat/module/couchbase/node/node.go b/metricbeat/module/couchbase/node/node.go index fcef19ffc34..eb6ae3590ce 100644 --- a/metricbeat/module/couchbase/node/node.go +++ b/metricbeat/module/couchbase/node/node.go @@ -36,7 +36,7 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The couchbase node metricset is experimental") + logp.Warn("BETA: The couchbase node metricset is beta") return &MetricSet{ BaseMetricSet: base, diff --git a/metricbeat/module/docker/_meta/docs.asciidoc b/metricbeat/module/docker/_meta/docs.asciidoc index 1369b66cf52..56abe0cb616 100644 --- a/metricbeat/module/docker/_meta/docs.asciidoc +++ b/metricbeat/module/docker/_meta/docs.asciidoc @@ -1,6 +1,6 @@ == Docker Module -experimental[] +beta[] This module fetches metrics from https://www.docker.com/[Docker] containers. diff --git a/metricbeat/module/docker/_meta/fields.yml b/metricbeat/module/docker/_meta/fields.yml index 4dabcc01642..42f36702751 100644 --- a/metricbeat/module/docker/_meta/fields.yml +++ b/metricbeat/module/docker/_meta/fields.yml @@ -1,7 +1,7 @@ - key: docker title: "Docker" description: > - experimental[] + beta[] Docker stats collected from Docker. short_config: false diff --git a/metricbeat/module/docker/container/container.go b/metricbeat/module/docker/container/container.go index 0606072cdfc..2ad0cfb9e40 100644 --- a/metricbeat/module/docker/container/container.go +++ b/metricbeat/module/docker/container/container.go @@ -22,7 +22,7 @@ type MetricSet struct { // New creates a new instance of the docker container MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker container metricset is experimental") + logp.Warn("BETA: The docker container metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/cpu/cpu.go b/metricbeat/module/docker/cpu/cpu.go index b945c039117..f5932fba30d 100644 --- a/metricbeat/module/docker/cpu/cpu.go +++ b/metricbeat/module/docker/cpu/cpu.go @@ -23,7 +23,7 @@ type MetricSet struct { // New creates a new instance of the docker cpu MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker cpu metricset is experimental") + logp.Warn("BETA: The docker cpu metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/diskio/diskio.go b/metricbeat/module/docker/diskio/diskio.go index a0e36b5dd96..15688b4fde4 100644 --- a/metricbeat/module/docker/diskio/diskio.go +++ b/metricbeat/module/docker/diskio/diskio.go @@ -23,7 +23,7 @@ type MetricSet struct { // New create a new instance of the docker diskio MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker diskio metricset is experimental") + logp.Warn("BETA: The docker diskio metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/healthcheck/healthcheck.go b/metricbeat/module/docker/healthcheck/healthcheck.go index f487e2708bb..f3624b7202e 100644 --- a/metricbeat/module/docker/healthcheck/healthcheck.go +++ b/metricbeat/module/docker/healthcheck/healthcheck.go @@ -22,7 +22,7 @@ type MetricSet struct { // New creates a new instance of the docker healthcheck MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker healthcheck metricset is experimental") + logp.Warn("BETA: The docker healthcheck metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/image/image.go b/metricbeat/module/docker/image/image.go index ebac4544192..59caf643c1d 100644 --- a/metricbeat/module/docker/image/image.go +++ b/metricbeat/module/docker/image/image.go @@ -31,7 +31,7 @@ type MetricSet struct { // configuration entries if needed. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker info metricset is experimental") + logp.Warn("BETA: The docker info metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/info/_meta/fields.yml b/metricbeat/module/docker/info/_meta/fields.yml index 76dfcb40291..08f7145fbd4 100644 --- a/metricbeat/module/docker/info/_meta/fields.yml +++ b/metricbeat/module/docker/info/_meta/fields.yml @@ -1,7 +1,7 @@ - name: info type: group description: > - experimental[] + beta[] Info metrics based on https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/display-system-wide-information. fields: diff --git a/metricbeat/module/docker/info/info.go b/metricbeat/module/docker/info/info.go index dc4dafc0bf7..f12b77115b4 100644 --- a/metricbeat/module/docker/info/info.go +++ b/metricbeat/module/docker/info/info.go @@ -22,7 +22,7 @@ type MetricSet struct { // New create a new instance of the docker info MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker info metricset is experimental") + logp.Warn("BETA: The docker info metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/memory/memory.go b/metricbeat/module/docker/memory/memory.go index 511828825c2..192083f32d5 100644 --- a/metricbeat/module/docker/memory/memory.go +++ b/metricbeat/module/docker/memory/memory.go @@ -23,7 +23,7 @@ type MetricSet struct { // New creates a new instance of the docker memory MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker memory metricset is experimental") + logp.Warn("BETA: The docker memory metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/docker/network/network.go b/metricbeat/module/docker/network/network.go index 8c8aa13d8b5..5119cb19539 100644 --- a/metricbeat/module/docker/network/network.go +++ b/metricbeat/module/docker/network/network.go @@ -23,7 +23,7 @@ type MetricSet struct { // New creates a new instance of the docker network MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The docker network metricset is experimental") + logp.Warn("BETA: The docker network metricset is beta") config := docker.Config{} if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/haproxy/_meta/fields.yml b/metricbeat/module/haproxy/_meta/fields.yml index fe6b5616808..e854e0f475d 100644 --- a/metricbeat/module/haproxy/_meta/fields.yml +++ b/metricbeat/module/haproxy/_meta/fields.yml @@ -1,8 +1,6 @@ - key: haproxy title: "HAProxy" description: > - experimental[] - HAProxy Module short_config: false fields: diff --git a/metricbeat/module/haproxy/info/info.go b/metricbeat/module/haproxy/info/info.go index ca50b411a5d..b7abfba1eeb 100644 --- a/metricbeat/module/haproxy/info/info.go +++ b/metricbeat/module/haproxy/info/info.go @@ -31,8 +31,6 @@ type MetricSet struct { // New creates a haproxy info MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The %v %v metricset is experimental", base.Module().Name(), base.Name()) - return &MetricSet{BaseMetricSet: base}, nil } diff --git a/metricbeat/module/haproxy/stat/stat.go b/metricbeat/module/haproxy/stat/stat.go index 7b107cc6ead..85656d472c8 100644 --- a/metricbeat/module/haproxy/stat/stat.go +++ b/metricbeat/module/haproxy/stat/stat.go @@ -31,8 +31,6 @@ type MetricSet struct { // New creates a new haproxy stat MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The %v %v metricset is experimental", base.Module().Name(), base.Name()) - return &MetricSet{BaseMetricSet: base}, nil } diff --git a/metricbeat/module/kafka/_meta/docs.asciidoc b/metricbeat/module/kafka/_meta/docs.asciidoc index b5127454877..f703fbeb18f 100644 --- a/metricbeat/module/kafka/_meta/docs.asciidoc +++ b/metricbeat/module/kafka/_meta/docs.asciidoc @@ -1,6 +1,6 @@ == kafka Module -experimental[] +beta[] This is the kafka Module. diff --git a/metricbeat/module/kafka/_meta/fields.yml b/metricbeat/module/kafka/_meta/fields.yml index 55da41efa75..c12923d65f8 100644 --- a/metricbeat/module/kafka/_meta/fields.yml +++ b/metricbeat/module/kafka/_meta/fields.yml @@ -3,7 +3,7 @@ description: > kafka Module - experimental[] + beta[] short_config: false fields: - name: kafka diff --git a/metricbeat/module/kafka/consumergroup/consumergroup.go b/metricbeat/module/kafka/consumergroup/consumergroup.go index 3fcb81a8546..21da39e31d4 100644 --- a/metricbeat/module/kafka/consumergroup/consumergroup.go +++ b/metricbeat/module/kafka/consumergroup/consumergroup.go @@ -36,7 +36,7 @@ var debugf = logp.MakeDebug("kafka") // New creates a new instance of the MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The kafka consumergroup metricset is experimental") + logp.Warn("BETA: The kafka consumergroup metricset is beta") config := defaultConfig if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/kafka/partition/partition.go b/metricbeat/module/kafka/partition/partition.go index a29b36547a9..ebccfa46ef4 100644 --- a/metricbeat/module/kafka/partition/partition.go +++ b/metricbeat/module/kafka/partition/partition.go @@ -37,7 +37,7 @@ var debugf = logp.MakeDebug("kafka") // New creates a new instance of the partition MetricSet. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The kafka partition metricset is experimental") + logp.Warn("BETA: The kafka partition metricset is beta") config := defaultConfig if err := base.Module().UnpackConfig(&config); err != nil { diff --git a/metricbeat/module/php_fpm/_meta/docs.asciidoc b/metricbeat/module/php_fpm/_meta/docs.asciidoc index 4cd1272731a..9e70d878088 100644 --- a/metricbeat/module/php_fpm/_meta/docs.asciidoc +++ b/metricbeat/module/php_fpm/_meta/docs.asciidoc @@ -1,5 +1,7 @@ == PHP-FPM Module +beta[] + This module periodically fetches metrics from https://php-fpm.org[PHP-FPM] servers. diff --git a/metricbeat/module/php_fpm/_meta/fields.yml b/metricbeat/module/php_fpm/_meta/fields.yml index a8a8ac8ea36..42f012a060a 100644 --- a/metricbeat/module/php_fpm/_meta/fields.yml +++ b/metricbeat/module/php_fpm/_meta/fields.yml @@ -1,9 +1,9 @@ - key: php_fpm title: "php_fpm" description: > - PHP-FPM server status metrics collected from PHP-FPM. + beta[] - experimental[] + PHP-FPM server status metrics collected from PHP-FPM. short_config: false fields: - name: php_fpm diff --git a/metricbeat/module/php_fpm/pool/pool.go b/metricbeat/module/php_fpm/pool/pool.go index bb1ede66fe6..e24c227d119 100644 --- a/metricbeat/module/php_fpm/pool/pool.go +++ b/metricbeat/module/php_fpm/pool/pool.go @@ -39,7 +39,7 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The php-fpm pool metricset is experimental") + logp.Warn("BETA: The php-fpm pool metricset is beta") return &MetricSet{ base, helper.NewHTTP(base), diff --git a/metricbeat/module/prometheus/_meta/docs.asciidoc b/metricbeat/module/prometheus/_meta/docs.asciidoc index 8ad5916bb90..43d59f5bbca 100644 --- a/metricbeat/module/prometheus/_meta/docs.asciidoc +++ b/metricbeat/module/prometheus/_meta/docs.asciidoc @@ -1,4 +1,6 @@ == prometheus Module +beta[] + This is the prometheus Module. diff --git a/metricbeat/module/prometheus/_meta/fields.yml b/metricbeat/module/prometheus/_meta/fields.yml index 1cfca1182c6..fa1632f9e48 100644 --- a/metricbeat/module/prometheus/_meta/fields.yml +++ b/metricbeat/module/prometheus/_meta/fields.yml @@ -1,9 +1,9 @@ - key: prometheus title: "Prometheus" description: > - Prometheus Module + beta[] - experimental[] + Prometheus Module short_config: false fields: - name: prometheus diff --git a/metricbeat/module/prometheus/collector/collector.go b/metricbeat/module/prometheus/collector/collector.go index 44866382c57..0aa24debb5b 100644 --- a/metricbeat/module/prometheus/collector/collector.go +++ b/metricbeat/module/prometheus/collector/collector.go @@ -34,7 +34,7 @@ type MetricSet struct { } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The prometheus collector metricset is experimental") + logp.Warn("BETA: The prometheus collector metricset is beta") config := struct { Namespace string `config:"namespace" validate:"required"` diff --git a/metricbeat/module/prometheus/stats/stats.go b/metricbeat/module/prometheus/stats/stats.go index 661eae1df8b..ad6b65dd953 100644 --- a/metricbeat/module/prometheus/stats/stats.go +++ b/metricbeat/module/prometheus/stats/stats.go @@ -34,7 +34,7 @@ type MetricSet struct { } func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - logp.Warn("EXPERIMENTAL: The prometheus stats metricset is experimental") + logp.Warn("BETA: The prometheus stats metricset is beta") return &MetricSet{ BaseMetricSet: base, diff --git a/metricbeat/tests/system/test_docker.py b/metricbeat/tests/system/test_docker.py index be883f32203..02440c680d6 100644 --- a/metricbeat/tests/system/test_docker.py +++ b/metricbeat/tests/system/test_docker.py @@ -24,7 +24,7 @@ def test_container_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -50,7 +50,7 @@ def test_cpu_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -80,7 +80,7 @@ def test_diskio_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -107,7 +107,7 @@ def test_info_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -132,7 +132,7 @@ def test_memory_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -158,7 +158,7 @@ def test_network_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -184,7 +184,7 @@ def test_health_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] @@ -210,7 +210,7 @@ def test_image_fields(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() evt = output[0] diff --git a/metricbeat/tests/system/test_haproxy.py b/metricbeat/tests/system/test_haproxy.py index 505c2759a2c..558a1f9a060 100644 --- a/metricbeat/tests/system/test_haproxy.py +++ b/metricbeat/tests/system/test_haproxy.py @@ -25,7 +25,7 @@ def test_info(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log, "ERR|WARN") output = self.read_output_json() self.assertEqual(len(output), 1) @@ -52,7 +52,7 @@ def test_stat(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log, "ERR|WARN") output = self.read_output_json() self.assertGreater(len(output), 0) diff --git a/metricbeat/tests/system/test_phpfpm.py b/metricbeat/tests/system/test_phpfpm.py index 6d4c46a8ef1..96c29ec5ebc 100644 --- a/metricbeat/tests/system/test_phpfpm.py +++ b/metricbeat/tests/system/test_phpfpm.py @@ -1,7 +1,6 @@ import os import metricbeat import unittest -from nose.plugins.attrib import attr PHPFPM_FIELDS = metricbeat.COMMON_FIELDS + ["php_fpm"] @@ -25,7 +24,7 @@ def test_info(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() self.assertEqual(len(output), 1) diff --git a/metricbeat/tests/system/test_prometheus.py b/metricbeat/tests/system/test_prometheus.py index 972d659ccc9..f00407fba4c 100644 --- a/metricbeat/tests/system/test_prometheus.py +++ b/metricbeat/tests/system/test_prometheus.py @@ -1,7 +1,6 @@ import os import metricbeat import unittest -from nose.plugins.attrib import attr PROMETHEUS_FIELDS = metricbeat.COMMON_FIELDS + ["prometheus"] @@ -25,7 +24,7 @@ def test_stats(self): # Ensure no errors or warnings exist in the log. log = self.get_log() - self.assertNotRegexpMatches(log.replace("WARN EXPERIMENTAL", ""), "ERR|WARN") + self.assertNotRegexpMatches(log.replace("WARN BETA", ""), "ERR|WARN") output = self.read_output_json() self.assertEqual(len(output), 1) From 85b1e385e1d486df0ca8861b200aca31a30c2595 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 7 Feb 2017 08:16:47 +0100 Subject: [PATCH 65/78] Add fileset.name and fileset.module fields (#3540) This replaces the `fields.source_type` hack with a `fileset` object that resembles the Metricbeat `metricset` object. The implementation still uses a hack: adds two hidden options to the prospector config, but it's the smaller evil IMHO. Part of #3159. --- filebeat/_meta/fields.common.yml | 7 +++++++ filebeat/docs/fields.asciidoc | 12 ++++++++++++ filebeat/filebeat.template-es2x.json | 14 ++++++++++++++ filebeat/filebeat.template.json | 12 ++++++++++++ filebeat/fileset/fileset.go | 10 ++++++++++ filebeat/fileset/fileset_test.go | 8 ++++++++ filebeat/harvester/config.go | 2 ++ filebeat/harvester/log.go | 2 ++ filebeat/input/event.go | 9 +++++++++ filebeat/module/apache2/access/config/access.yml | 2 -- filebeat/module/apache2/error/config/error.yml | 2 -- filebeat/module/mysql/error/config/error.yml | 2 -- filebeat/module/mysql/slowlog/config/slowlog.yml | 2 -- .../module/nginx/access/config/nginx-access.yml | 2 -- filebeat/module/nginx/error/config/nginx-error.yml | 2 -- filebeat/module/system/syslog/config/syslog.yml | 2 -- filebeat/tests/system/test_modules.py | 1 + 17 files changed, 77 insertions(+), 14 deletions(-) diff --git a/filebeat/_meta/fields.common.yml b/filebeat/_meta/fields.common.yml index dba1f92072a..54a37b7c2ee 100644 --- a/filebeat/_meta/fields.common.yml +++ b/filebeat/_meta/fields.common.yml @@ -44,3 +44,10 @@ the original `@timestamp` (representing the time when the log line was read) in this field. + - name: fileset.module + description: > + The Filebeat module that generated this event. + + - name: fileset.name + description: > + The Filebeat fileset that generated this event. diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index c409d8ef167..d933b336314 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -454,6 +454,18 @@ Ingestion pipeline error message, added in case there are errors reported by the In case the ingest pipeline parses the timestamp from the log contents, it stores the original `@timestamp` (representing the time when the log line was read) in this field. +[float] +=== fileset.module + +The Filebeat module that generated this event. + + +[float] +=== fileset.name + +The Filebeat fileset that generated this event. + + [[exported-fields-mysql]] == MySQL Fields diff --git a/filebeat/filebeat.template-es2x.json b/filebeat/filebeat.template-es2x.json index b22d17b688f..f0875e58ada 100644 --- a/filebeat/filebeat.template-es2x.json +++ b/filebeat/filebeat.template-es2x.json @@ -196,6 +196,20 @@ "fields": { "properties": {} }, + "fileset": { + "properties": { + "module": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "name": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + }, "input_type": { "ignore_above": 1024, "index": "not_analyzed", diff --git a/filebeat/filebeat.template.json b/filebeat/filebeat.template.json index 31de71d46b2..674fe10cb9d 100644 --- a/filebeat/filebeat.template.json +++ b/filebeat/filebeat.template.json @@ -165,6 +165,18 @@ "fields": { "properties": {} }, + "fileset": { + "properties": { + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "input_type": { "ignore_above": 1024, "type": "keyword" diff --git a/filebeat/fileset/fileset.go b/filebeat/fileset/fileset.go index 0938399aeb6..56c89bc0207 100644 --- a/filebeat/fileset/fileset.go +++ b/filebeat/fileset/fileset.go @@ -235,6 +235,16 @@ func (fs *Fileset) getProspectorConfig() (*common.Config, error) { return nil, fmt.Errorf("Error setting the pipeline ID in the prospector config: %v", err) } + // force our the module/fileset name + err = cfg.SetString("_module_name", -1, fs.mcfg.Module) + if err != nil { + return nil, fmt.Errorf("Error setting the _module_name cfg in the prospector config: %v", err) + } + err = cfg.SetString("_fileset_name", -1, fs.name) + if err != nil { + return nil, fmt.Errorf("Error setting the _fileset_name cfg in the prospector config: %v", err) + } + cfg.PrintDebugf("Merged prospector config for fileset %s/%s", fs.mcfg.Module, fs.name) return cfg, nil diff --git a/filebeat/fileset/fileset_test.go b/filebeat/fileset/fileset_test.go index 8ee77050264..a3a8b0c026b 100644 --- a/filebeat/fileset/fileset_test.go +++ b/filebeat/fileset/fileset_test.go @@ -185,6 +185,14 @@ func TestGetProspectorConfigNginxOverrides(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "filebeat-5.2.0-nginx-access-with_plugins", pipelineID) + moduleName, err := cfg.String("_module_name", -1) + assert.NoError(t, err) + assert.Equal(t, "nginx", moduleName) + + filesetName, err := cfg.String("_fileset_name", -1) + assert.NoError(t, err) + assert.Equal(t, "access", filesetName) + } func TestGetPipelineNginx(t *testing.T) { diff --git a/filebeat/harvester/config.go b/filebeat/harvester/config.go index c4c8068fe48..67668b6a140 100644 --- a/filebeat/harvester/config.go +++ b/filebeat/harvester/config.go @@ -53,6 +53,8 @@ type harvesterConfig struct { Multiline *reader.MultilineConfig `config:"multiline"` JSON *reader.JSONConfig `config:"json"` Pipeline string `config:"pipeline"` + Module string `config:"_module_name"` // hidden option to set the module name + Fileset string `config:"_fileset_name"` // hidden option to set the fileset name } func (config *harvesterConfig) Validate() error { diff --git a/filebeat/harvester/log.go b/filebeat/harvester/log.go index 7b8a4a2e775..ea7414b5763 100644 --- a/filebeat/harvester/log.go +++ b/filebeat/harvester/log.go @@ -138,6 +138,8 @@ func (h *Harvester) Harvest(r reader.Reader) { event.DocumentType = h.config.DocumentType event.JSONConfig = h.config.JSON event.Pipeline = h.config.Pipeline + event.Module = h.config.Module + event.Fileset = h.config.Fileset } // Always send event to update state, also if lines was skipped diff --git a/filebeat/input/event.go b/filebeat/input/event.go index 3d9a8852600..51267dba763 100644 --- a/filebeat/input/event.go +++ b/filebeat/input/event.go @@ -21,6 +21,8 @@ type Event struct { State file.State Data common.MapStr // Use in readers to add data to the event Pipeline string + Fileset string + Module string } func NewEvent(state file.State) *Event { @@ -39,6 +41,13 @@ func (e *Event) ToMapStr() common.MapStr { "input_type": e.InputType, } + if e.Fileset != "" && e.Module != "" { + event["fileset"] = common.MapStr{ + "name": e.Fileset, + "module": e.Module, + } + } + // Add data fields which are added by the readers for key, value := range e.Data { event[key] = value diff --git a/filebeat/module/apache2/access/config/access.yml b/filebeat/module/apache2/access/config/access.yml index 34d8fb14c2d..56e3dfd95b8 100644 --- a/filebeat/module/apache2/access/config/access.yml +++ b/filebeat/module/apache2/access/config/access.yml @@ -4,5 +4,3 @@ paths: - {{$path}} {{ end }} exclude_files: [".gz$"] -fields: - source_type: apache2-access diff --git a/filebeat/module/apache2/error/config/error.yml b/filebeat/module/apache2/error/config/error.yml index e2dc79a9435..56e3dfd95b8 100644 --- a/filebeat/module/apache2/error/config/error.yml +++ b/filebeat/module/apache2/error/config/error.yml @@ -4,5 +4,3 @@ paths: - {{$path}} {{ end }} exclude_files: [".gz$"] -fields: - source_type: apache2-error diff --git a/filebeat/module/mysql/error/config/error.yml b/filebeat/module/mysql/error/config/error.yml index 7986c05a466..56e3dfd95b8 100644 --- a/filebeat/module/mysql/error/config/error.yml +++ b/filebeat/module/mysql/error/config/error.yml @@ -4,5 +4,3 @@ paths: - {{$path}} {{ end }} exclude_files: [".gz$"] -fields: - source_type: mysql-error diff --git a/filebeat/module/mysql/slowlog/config/slowlog.yml b/filebeat/module/mysql/slowlog/config/slowlog.yml index 8ae68bd79c8..1a238b0a6d5 100644 --- a/filebeat/module/mysql/slowlog/config/slowlog.yml +++ b/filebeat/module/mysql/slowlog/config/slowlog.yml @@ -8,5 +8,3 @@ multiline: pattern: "^# User@Host: " negate: true match: after -fields: - source_type: mysql-slowlog diff --git a/filebeat/module/nginx/access/config/nginx-access.yml b/filebeat/module/nginx/access/config/nginx-access.yml index 6e79adcb43d..56e3dfd95b8 100644 --- a/filebeat/module/nginx/access/config/nginx-access.yml +++ b/filebeat/module/nginx/access/config/nginx-access.yml @@ -4,5 +4,3 @@ paths: - {{$path}} {{ end }} exclude_files: [".gz$"] -fields: - source_type: nginx-access diff --git a/filebeat/module/nginx/error/config/nginx-error.yml b/filebeat/module/nginx/error/config/nginx-error.yml index c878eeedc90..56e3dfd95b8 100644 --- a/filebeat/module/nginx/error/config/nginx-error.yml +++ b/filebeat/module/nginx/error/config/nginx-error.yml @@ -4,5 +4,3 @@ paths: - {{$path}} {{ end }} exclude_files: [".gz$"] -fields: - source_type: nginx-error diff --git a/filebeat/module/system/syslog/config/syslog.yml b/filebeat/module/system/syslog/config/syslog.yml index 65338c8da0c..1af7dee5e13 100644 --- a/filebeat/module/system/syslog/config/syslog.yml +++ b/filebeat/module/system/syslog/config/syslog.yml @@ -7,5 +7,3 @@ exclude_files: [".gz$"] multiline: pattern: "^\\s" match: after -fields: - source_type: system-syslog diff --git a/filebeat/tests/system/test_modules.py b/filebeat/tests/system/test_modules.py index 4dc7240b9a5..ab3554d03d6 100644 --- a/filebeat/tests/system/test_modules.py +++ b/filebeat/tests/system/test_modules.py @@ -97,6 +97,7 @@ def run_on_file(self, module, fileset, test_file, cfgfile): for obj in objects: self.assert_fields_are_documented(obj) # assert "error" not in obj # no parsing errors + assert obj["fileset"]["module"] == module if os.path.exists(test_file + "-expected.json"): with open(test_file + "-expected.json", "r") as f: From 397a674ecec484304a9db7d10f8b0f8101bdfee3 Mon Sep 17 00:00:00 2001 From: ruflin Date: Mon, 6 Feb 2017 15:13:06 +0100 Subject: [PATCH 66/78] Add docs for -once command line option Closes https://github.com/elastic/beats/issues/3104 --- filebeat/docs/command-line.asciidoc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/filebeat/docs/command-line.asciidoc b/filebeat/docs/command-line.asciidoc index 0120df3840d..32bf1b78d38 100644 --- a/filebeat/docs/command-line.asciidoc +++ b/filebeat/docs/command-line.asciidoc @@ -1,8 +1,13 @@ [[filebeat-command-line]] === Command Line Options -Filebeat does not have any Filebeat-specific command line options. Instead, you -configure the behaviour of Filebeat by specifying options in the configuration file. +The following command line option is specific to Filebeat. + +*`-once`*:: +When the `-once` flag is used, Filebeat starts all configured harvesters and prospectors, and runs +each prospector until the harvesters are closed. If you set the `-once` flag, you should also set +`close_eof` so the harvester is closed when the end of the file is reached. +By default harvesters are closed after `close_inactive` is reached. The following command line options from libbeat are also available for Filebeat. To use these options, you need to start Filebeat in the foreground. From 2c869e45fb62ab4f3bb03233128590645835cba7 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 11:08:23 +0100 Subject: [PATCH 67/78] Add known issue for filebeat (#3547) See https://github.com/elastic/beats/issues/3546 --- CHANGELOG.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1e0f4936bfd..1e15cc4bab8 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -105,6 +105,11 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Winlogbeat* +==== Knwon Issue + +*Filebeat* +- Prospector reloading only works properly with new files. {pull}3546[3546] + //////////////////////////////////////////////////////////// [[release-notes-5.2.0]] From 04f7147e3f14843865569b31f4c8968fc4e4301d Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 7 Feb 2017 13:21:35 +0100 Subject: [PATCH 68/78] Heartbeat HTTP sample dashboards (#3531) --- .../f3e771c0-eb19-11e6-be20-559646f8b9ba.json | 13 +++++++++++ .../c49bd160-eb17-11e6-be20-559646f8b9ba.json | 23 +++++++++++++++++++ .../091c3a90-eb1e-11e6-be20-559646f8b9ba.json | 11 +++++++++ .../0f4c0560-eb20-11e6-9f11-159ff202874a.json | 11 +++++++++ .../1738dbc0-eb1d-11e6-be20-559646f8b9ba.json | 11 +++++++++ .../920e8140-eb1a-11e6-be20-559646f8b9ba.json | 11 +++++++++ .../c65ef340-eb19-11e6-be20-559646f8b9ba.json | 11 +++++++++ 7 files changed, 91 insertions(+) create mode 100644 heartbeat/_meta/kibana/dashboard/f3e771c0-eb19-11e6-be20-559646f8b9ba.json create mode 100644 heartbeat/_meta/kibana/search/c49bd160-eb17-11e6-be20-559646f8b9ba.json create mode 100644 heartbeat/_meta/kibana/visualization/091c3a90-eb1e-11e6-be20-559646f8b9ba.json create mode 100644 heartbeat/_meta/kibana/visualization/0f4c0560-eb20-11e6-9f11-159ff202874a.json create mode 100644 heartbeat/_meta/kibana/visualization/1738dbc0-eb1d-11e6-be20-559646f8b9ba.json create mode 100644 heartbeat/_meta/kibana/visualization/920e8140-eb1a-11e6-be20-559646f8b9ba.json create mode 100644 heartbeat/_meta/kibana/visualization/c65ef340-eb19-11e6-be20-559646f8b9ba.json diff --git a/heartbeat/_meta/kibana/dashboard/f3e771c0-eb19-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/dashboard/f3e771c0-eb19-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..c0b3f531487 --- /dev/null +++ b/heartbeat/_meta/kibana/dashboard/f3e771c0-eb19-11e6-be20-559646f8b9ba.json @@ -0,0 +1,13 @@ +{ + "hits": 0, + "timeRestore": false, + "description": "", + "title": "Heartbeat HTTP monitoring", + "uiStateJSON": "{\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-5\":{\"vis\":{\"defaultColors\":{\"0 - 2\":\"rgb(247,251,255)\",\"2 - 3\":\"rgb(227,238,249)\",\"3 - 4\":\"rgb(208,225,242)\",\"4 - 5\":\"rgb(182,212,233)\",\"5 - 6\":\"rgb(148,196,223)\",\"6 - 8\":\"rgb(107,174,214)\",\"8 - 9\":\"rgb(74,152,201)\",\"9 - 10\":\"rgb(46,126,188)\",\"10 - 11\":\"rgb(23,100,171)\",\"11 - 12\":\"rgb(8,74,145)\"}}}}", + "panelsJSON": "[{\"col\":1,\"id\":\"c65ef340-eb19-11e6-be20-559646f8b9ba\",\"panelIndex\":1,\"row\":7,\"size_x\":12,\"size_y\":4,\"type\":\"visualization\"},{\"col\":9,\"id\":\"920e8140-eb1a-11e6-be20-559646f8b9ba\",\"panelIndex\":2,\"row\":1,\"size_x\":4,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"1738dbc0-eb1d-11e6-be20-559646f8b9ba\",\"panelIndex\":3,\"row\":1,\"size_x\":8,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"091c3a90-eb1e-11e6-be20-559646f8b9ba\",\"panelIndex\":4,\"row\":5,\"size_x\":12,\"size_y\":2,\"type\":\"visualization\"},{\"col\":1,\"id\":\"0f4c0560-eb20-11e6-9f11-159ff202874a\",\"panelIndex\":5,\"row\":11,\"size_x\":12,\"size_y\":5,\"type\":\"visualization\"}]", + "optionsJSON": "{\"darkTheme\":false}", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}]}" + } +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/search/c49bd160-eb17-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/search/c49bd160-eb17-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..bb7b085dc25 --- /dev/null +++ b/heartbeat/_meta/kibana/search/c49bd160-eb17-11e6-be20-559646f8b9ba.json @@ -0,0 +1,23 @@ +{ + "sort": [ + "@timestamp", + "desc" + ], + "hits": 0, + "description": "", + "title": "Heartbeat HTTP pings", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"heartbeat-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[{\"meta\":{\"negate\":false,\"index\":\"heartbeat-*\",\"key\":\"type\",\"value\":\"http\",\"disabled\":false,\"alias\":null},\"query\":{\"match\":{\"type\":{\"query\":\"http\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}" + }, + "columns": [ + "monitor", + "up", + "response.status", + "duration.us", + "tcp_connect_rtt.us", + "tls_handshake_rtt.us", + "resolve_rtt.us", + "http_rtt.us" + ] +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/visualization/091c3a90-eb1e-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/visualization/091c3a90-eb1e-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..e953f3f9a7f --- /dev/null +++ b/heartbeat/_meta/kibana/visualization/091c3a90-eb1e-11e6-be20-559646f8b9ba.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"HTTP up status\",\"type\":\"area\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"percentage\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"up:true\",\"analyze_wildcard\":true}}},\"label\":\"\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"up:false\",\"analyze_wildcard\":true}}}}]}}],\"listeners\":{}}", + "description": "", + "title": "HTTP up status", + "uiStateJSON": "{\"vis\":{\"colors\":{\"up:false\":\"#BF1B00\",\"up:true\":\"#629E51\"}}}", + "version": 1, + "savedSearchId": "c49bd160-eb17-11e6-be20-559646f8b9ba", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[]}" + } +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/visualization/0f4c0560-eb20-11e6-9f11-159ff202874a.json b/heartbeat/_meta/kibana/visualization/0f4c0560-eb20-11e6-9f11-159ff202874a.json new file mode 100644 index 00000000000..087f16bacbe --- /dev/null +++ b/heartbeat/_meta/kibana/visualization/0f4c0560-eb20-11e6-9f11-159ff202874a.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"HTTP duration heatmap\",\"type\":\"heatmap\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"enableHover\":false,\"legendPosition\":\"right\",\"times\":[],\"colorsNumber\":10,\"colorSchema\":\"Blues\",\"setColorRange\":false,\"colorsRange\":[],\"invertColors\":false,\"percentageMode\":false,\"valueAxes\":[{\"show\":false,\"id\":\"ValueAxis-1\",\"type\":\"value\",\"scale\":{\"type\":\"linear\",\"defaultYExtents\":false},\"labels\":{\"show\":false,\"rotate\":0,\"color\":\"#555\"}}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"histogram\",\"schema\":\"group\",\"params\":{\"field\":\"duration.us\",\"interval\":50000,\"extended_bounds\":{}}}],\"listeners\":{}}", + "description": "", + "title": "HTTP duration heatmap", + "uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 2\":\"rgb(247,251,255)\",\"2 - 3\":\"rgb(227,238,249)\",\"3 - 4\":\"rgb(208,225,242)\",\"4 - 5\":\"rgb(182,212,233)\",\"5 - 6\":\"rgb(148,196,223)\",\"6 - 8\":\"rgb(107,174,214)\",\"8 - 9\":\"rgb(74,152,201)\",\"9 - 10\":\"rgb(46,126,188)\",\"10 - 11\":\"rgb(23,100,171)\",\"11 - 12\":\"rgb(8,74,145)\"}}}", + "version": 1, + "savedSearchId": "c49bd160-eb17-11e6-be20-559646f8b9ba", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[]}" + } +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/visualization/1738dbc0-eb1d-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/visualization/1738dbc0-eb1d-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..dca9fa71bff --- /dev/null +++ b/heartbeat/_meta/kibana/visualization/1738dbc0-eb1d-11e6-be20-559646f8b9ba.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"HTTP monitors\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"duration.us\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"monitor\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"5\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"resolve_rtt.us\"}},{\"id\":\"6\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"tcp_connect_rtt.us\"}},{\"id\":\"7\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"tls_handshake_rtt.us\"}},{\"id\":\"8\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"http_rtt.us\"}}],\"listeners\":{}}", + "description": "", + "title": "HTTP monitors", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "savedSearchId": "c49bd160-eb17-11e6-be20-559646f8b9ba", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[]}" + } +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/visualization/920e8140-eb1a-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/visualization/920e8140-eb1a-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..a5b25108c21 --- /dev/null +++ b/heartbeat/_meta/kibana/visualization/920e8140-eb1a-11e6-be20-559646f8b9ba.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"HTTP monitors status\",\"type\":\"pie\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"bottom\",\"isDonut\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"monitor\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"segment\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"up: true\",\"analyze_wildcard\":true}}},\"label\":\"\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"up: false\",\"analyze_wildcard\":true}}}}]}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"response.status\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "HTTP monitors status", + "uiStateJSON": "{\"vis\":{\"colors\":{\"200\":\"#B7DBAB\",\"up: true\":\"#629E51\",\"up: false\":\"#E24D42\"},\"legendOpen\":true}}", + "version": 1, + "savedSearchId": "c49bd160-eb17-11e6-be20-559646f8b9ba", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[]}" + } +} \ No newline at end of file diff --git a/heartbeat/_meta/kibana/visualization/c65ef340-eb19-11e6-be20-559646f8b9ba.json b/heartbeat/_meta/kibana/visualization/c65ef340-eb19-11e6-be20-559646f8b9ba.json new file mode 100644 index 00000000000..d62474012ac --- /dev/null +++ b/heartbeat/_meta/kibana/visualization/c65ef340-eb19-11e6-be20-559646f8b9ba.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"HTTP ping times\",\"type\":\"area\",\"params\":{\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"resolve_rtt.us\",\"customLabel\":\"\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"tcp_connect_rtt.us\"}},{\"id\":\"5\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"tls_handshake_rtt.us\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"max\",\"schema\":\"metric\",\"params\":{\"field\":\"http_rtt.us\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "description": "", + "title": "HTTP ping times", + "uiStateJSON": "{}", + "version": 1, + "savedSearchId": "c49bd160-eb17-11e6-be20-559646f8b9ba", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[]}" + } +} \ No newline at end of file From 72ff1786ba6616ed4d1f41863dc889300b66c64d Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 13:22:08 +0100 Subject: [PATCH 69/78] Apply close_timeout also when output is blocked (#3511) Currently `close_timeout` does not apply in case the output is blocked. This PR changes the behavior of `close_timeout` to also close a file handler when the output is blocked. It is important to note, that this closes the file handler but NOT the harvester. This is important as the closing of the harvester requires a state update to set `state.Finished=true`. If this would not happen and the harvester is closed, processing would not continue when the output becomes available again. Previously the internal state of a harvester was updated when the event was created. This could lead to the issue that in case an event was not sent but the state update went through, that an event would be missing. This is now prevent by overwriting the internal state only when the event was successfully sent. The done channels from prospector and harvester are renamed to be more obvious which one belongs to what: h.done -> h.prospectorDone, h.harvestDone -> h.done. As the harvester channel is close with the `stop` method in all cases `h.done` is sufficient in most places. This PR does not solve the problem related to reloading and stopping a harvester mentioned in https://github.com/elastic/beats/pull/3511#issuecomment-277264760 related to reloading. This will be done in a follow up PR. --- CHANGELOG.asciidoc | 1 + filebeat/harvester/harvester.go | 6 ++- filebeat/harvester/log.go | 65 ++++++++++++++++--------- filebeat/harvester/log_test.go | 1 - filebeat/tests/system/test_multiline.py | 6 +-- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1e15cc4bab8..58d0e139ef7 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -67,6 +67,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add the `pipeline` config option at the prospector level, for configuring the Ingest Node pipeline ID. {pull}3433[3433] - Update regular expressions used for matching file names or lines (multiline, include/exclude functionality) to new matchers improving performance of simple string matches. {pull}3469[3469] - The `symlinks` and `harverster_limit` settings are now GA, instead of experimental. {pull}3525[3525] +- close_timeout is also applied when the output is blocking. {pull}3511[3511] *Heartbeat* diff --git a/filebeat/harvester/harvester.go b/filebeat/harvester/harvester.go index 02525294ba6..3e9755f1662 100644 --- a/filebeat/harvester/harvester.go +++ b/filebeat/harvester/harvester.go @@ -14,6 +14,7 @@ package harvester import ( "errors" "fmt" + "sync" "github.com/elastic/beats/filebeat/config" "github.com/elastic/beats/filebeat/harvester/encoding" @@ -39,6 +40,8 @@ type Harvester struct { fileReader *LogFile encodingFactory encoding.EncodingFactory encoding encoding.Encoding + prospectorDone chan struct{} + once sync.Once done chan struct{} } @@ -53,7 +56,8 @@ func NewHarvester( config: defaultConfig, state: state, prospectorChan: prospectorChan, - done: done, + prospectorDone: done, + done: make(chan struct{}), } if err := cfg.Unpack(&h.config); err != nil { diff --git a/filebeat/harvester/log.go b/filebeat/harvester/log.go index ea7414b5763..95027045d5e 100644 --- a/filebeat/harvester/log.go +++ b/filebeat/harvester/log.go @@ -58,13 +58,14 @@ func (h *Harvester) Harvest(r reader.Reader) { defer h.close() // Channel to stop internal harvester routines - harvestDone := make(chan struct{}) - defer close(harvestDone) + defer h.stop() // Closes reader after timeout or when done channel is closed // This routine is also responsible to properly stop the reader go func() { - var closeTimeout <-chan time.Time + + closeTimeout := make(<-chan time.Time) + // starts close_timeout timer if h.config.CloseTimeout > 0 { closeTimeout = time.After(h.config.CloseTimeout) } @@ -72,12 +73,14 @@ func (h *Harvester) Harvest(r reader.Reader) { select { // Applies when timeout is reached case <-closeTimeout: - logp.Info("Closing harvester because close_timeout was reached: %s", h.state.Source) + logp.Info("Closing harvester because close_timeout was reached.") // Required for shutdown when hanging inside reader - case <-h.done: + case <-h.prospectorDone: // Required when reader loop returns and reader finished - case <-harvestDone: + case <-h.done: } + + h.stop() h.fileReader.Close() }() @@ -122,9 +125,10 @@ func (h *Harvester) Harvest(r reader.Reader) { // Update offset h.state.Offset += int64(message.Bytes) - // Create state event - event := input.NewEvent(h.getState()) + state := h.getState() + // Create state event + event := input.NewEvent(state) text := string(message.Content) // Check if data should be added to event. Only export non empty events. @@ -147,12 +151,21 @@ func (h *Harvester) Harvest(r reader.Reader) { if !h.sendEvent(event) { return } + // Update state of harvester as successfully sent + h.state = state } } +func (h *Harvester) stop() { + h.once.Do(func() { + close(h.done) + }) +} + // sendEvent sends event to the spooler channel // Return false if event was not sent func (h *Harvester) sendEvent(event *input.Event) bool { + select { case <-h.done: return false @@ -161,6 +174,21 @@ func (h *Harvester) sendEvent(event *input.Event) bool { } } +// sendStateUpdate send an empty event with the current state to update the registry +// close_timeout does not apply here to make sure a harvester is closed properly. In +// case the output is blocked the harvester will stay open to make sure no new harvester +// is started. As soon as the output becomes available again, the finished state is written +// and processing can continue. +func (h *Harvester) sendStateUpdate() { + logp.Debug("harvester", "Update state: %s, offset: %v", h.state.Source, h.state.Offset) + event := input.NewEvent(h.state) + + select { + case <-h.prospectorDone: + case h.prospectorChan <- event: // ship the new event downstream + } +} + // shouldExportLine decides if the line is exported or not based on // the include_lines and exclude_lines options. func (h *Harvester) shouldExportLine(line string) bool { @@ -260,22 +288,18 @@ func (h *Harvester) initFileOffset(file *os.File) (int64, error) { return file.Seek(0, os.SEEK_CUR) } -// sendStateUpdate send an empty event with the current state to update the registry -func (h *Harvester) sendStateUpdate() bool { - logp.Debug("harvester", "Update state: %s, offset: %v", h.state.Source, h.state.Offset) - event := input.NewEvent(h.getState()) - return h.sendEvent(event) -} - +// getState returns an updated copy of the harvester state func (h *Harvester) getState() file.State { if h.config.InputType == config.StdinInputType { return file.State{} } + state := h.state + // refreshes the values in State with the values from the harvester itself - h.state.FileStateOS = file.GetOSState(h.state.Fileinfo) - return h.state + state.FileStateOS = file.GetOSState(h.state.Fileinfo) + return state } func (h *Harvester) close() { @@ -289,6 +313,7 @@ func (h *Harvester) close() { // If file was never opened, it can't be closed if h.file != nil { + // close file handler h.file.Close() logp.Debug("harvester", "Closing file: %s", h.state.Source) @@ -350,9 +375,3 @@ func (h *Harvester) newLogFileReader() (reader.Reader, error) { return reader.NewLimit(r, h.config.MaxBytes), nil } - -/* - -TODO: introduce new structure: log_file —[raw bytes]—> (line —[utf8 bytes]—> encode) —[message]—> …` - -*/ diff --git a/filebeat/harvester/log_test.go b/filebeat/harvester/log_test.go index 8862b15c8dc..c08a7ace01c 100644 --- a/filebeat/harvester/log_test.go +++ b/filebeat/harvester/log_test.go @@ -69,7 +69,6 @@ func TestReadLine(t *testing.T) { }, file: f, } - assert.NotNil(t, h) var ok bool h.encodingFactory, ok = encoding.FindEncoding(h.config.Encoding) diff --git a/filebeat/tests/system/test_multiline.py b/filebeat/tests/system/test_multiline.py index 74d9fc5d3b4..064d2f46cae 100644 --- a/filebeat/tests/system/test_multiline.py +++ b/filebeat/tests/system/test_multiline.py @@ -247,7 +247,7 @@ def test_close_timeout_with_multiline(self): pattern="^\[", negate="true", match="after", - close_timeout="1s", + close_timeout="2s", ) os.mkdir(self.working_dir + "/log/") @@ -286,7 +286,7 @@ def test_close_timeout_with_multiline(self): # close_timeout must have closed the reader exactly twice self.wait_until( lambda: self.log_contains_count( - "Closing harvester because close_timeout was reached") == 2, + "Closing harvester because close_timeout was reached") >= 1, max_timeout=15) output = self.read_output() @@ -302,7 +302,7 @@ def test_consecutive_newline(self): pattern="^\[", negate="true", match="after", - close_timeout="1s", + close_timeout="2s", ) logentry1 = """[2016-09-02 19:54:23 +0000] Started 2016-09-02 19:54:23 +0000 "GET" for /gaq?path=%2FCA%2FFallbrook%2F1845-Acacia-Ln&referer=http%3A%2F%2Fwww.xxxxx.com%2FAcacia%2BLn%2BFallbrook%2BCA%2Baddresses&search_bucket=none&page_controller=v9%2Faddresses&page_action=show at 23.235.47.31 From f7cc4c7f4e2d6becf15847f02187235e56cb8a96 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Tue, 7 Feb 2017 16:11:00 +0100 Subject: [PATCH 70/78] Update go-ucfg to version 0.4.6 (#3549) --- glide.yaml | 2 +- .../github.com/elastic/go-ucfg/CHANGELOG.md | 12 +++- vendor/github.com/elastic/go-ucfg/merge.go | 14 +++++ vendor/github.com/elastic/go-ucfg/reify.go | 15 ++++- vendor/github.com/elastic/go-ucfg/ucfg.go | 1 + vendor/github.com/elastic/go-ucfg/unpack.go | 63 ++++++++++++++++++- vendor/github.com/elastic/go-ucfg/util.go | 3 + 7 files changed, 105 insertions(+), 5 deletions(-) diff --git a/glide.yaml b/glide.yaml index f5997e17eb7..2aba4ec016b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -83,7 +83,7 @@ import: - package: github.com/dustin/go-humanize version: 499693e27ee0d14ffab67c31ad065fdb3d34ea75 - package: github.com/elastic/go-ucfg - version: v0.4.5 + version: v0.4.6 - package: github.com/armon/go-socks5 version: 3a873e99f5400ad7706e464e905ffcc94b3ff247 - package: github.com/pkg/errors diff --git a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md index de0633e2922..b3359fea494 100644 --- a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md +++ b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md @@ -14,6 +14,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [0.4.6] + +### Added +- Introduce ,ignore struct tag option to optionally ignore exported fields. #89 +- Add support for custom Unpacker method with `*Config` being convertible to first parameter. The custom method must be compatible to `ConfigUnpacker`. #90 + +### Fixed +- Ignore private struct fields when merging a struct into a config. #89 + ## [0.4.5] ### Changed @@ -168,7 +177,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Introduced CHANGELOG.md for documenting changes to ucfg. -[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.4.5...HEAD +[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.4.6...HEAD +[0.4.6]: https://github.com/elastic/go-ucfg/compare/v0.4.5...v0.4.6 [0.4.5]: https://github.com/elastic/go-ucfg/compare/v0.4.4...v0.4.5 [0.4.4]: https://github.com/elastic/go-ucfg/compare/v0.4.3...v0.4.4 [0.4.3]: https://github.com/elastic/go-ucfg/compare/v0.4.2...v0.4.3 diff --git a/vendor/github.com/elastic/go-ucfg/merge.go b/vendor/github.com/elastic/go-ucfg/merge.go index 34d149faf65..f4ad4411e37 100644 --- a/vendor/github.com/elastic/go-ucfg/merge.go +++ b/vendor/github.com/elastic/go-ucfg/merge.go @@ -5,6 +5,8 @@ import ( "reflect" "regexp" "time" + "unicode" + "unicode/utf8" ) // Merge a map, a slice, a struct or another Config object into c. @@ -40,6 +42,9 @@ import ( // // struct, a slice, an array, a map or of type *Config) // Field map[string]interface{} `config:",inline"` // +// // field is ignored by Merge +// Field string `config:",ignore"` +// // // Returns an error if merging fails to normalize and validate the from value. // If duplicate setting names are detected in the input, merging fails as well. @@ -232,7 +237,16 @@ func normalizeStructInto(cfg *Config, opts *options, from reflect.Value) Error { for i := 0; i < numField; i++ { var err Error stField := v.Type().Field(i) + + // ignore non exported fields + if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { + continue + } + name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) + if tagOpts.ignore { + continue + } if tagOpts.squash { vField := chaseValue(v.Field(i)) diff --git a/vendor/github.com/elastic/go-ucfg/reify.go b/vendor/github.com/elastic/go-ucfg/reify.go index 493f0da2e77..d28e68d7a35 100644 --- a/vendor/github.com/elastic/go-ucfg/reify.go +++ b/vendor/github.com/elastic/go-ucfg/reify.go @@ -4,6 +4,8 @@ import ( "reflect" "regexp" "time" + "unicode" + "unicode/utf8" ) // Unpack unpacks c into a struct, a map, or a slice allocating maps, slices, @@ -56,6 +58,9 @@ import ( // A field its name is set using the `config` struct tag (configured by StructTag) // If tag is missing or no field name is configured in the tag, the field name // itself will be used. +// If the tag sets the `,ignore` flag, the field will not be overwritten. +// If the tag sets the `,inline` or `,squash` flag, Unpack will apply the current +// configuration namespace to the fields. // // // Fields available in a struct or a map, but not in the Config object, will not @@ -197,9 +202,17 @@ func reifyStruct(opts *options, orig reflect.Value, cfg *Config) Error { numField := to.NumField() for i := 0; i < numField; i++ { stField := to.Type().Field(i) - vField := to.Field(i) + + // ignore non exported fields + if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { + continue + } name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) + if tagOpts.ignore { + continue + } + vField := to.Field(i) validators, err := parseValidatorTags(stField.Tag.Get(opts.validatorTag)) if err != nil { return raiseCritical(err, "") diff --git a/vendor/github.com/elastic/go-ucfg/ucfg.go b/vendor/github.com/elastic/go-ucfg/ucfg.go index e959d5c0fb5..f0ee3bcef0e 100644 --- a/vendor/github.com/elastic/go-ucfg/ucfg.go +++ b/vendor/github.com/elastic/go-ucfg/ucfg.go @@ -45,6 +45,7 @@ var ( tInterfaceArray = reflect.TypeOf([]interface{}(nil)) // interface types + tError = reflect.TypeOf((*error)(nil)).Elem() tValidator = reflect.TypeOf((*Validator)(nil)).Elem() // primitives diff --git a/vendor/github.com/elastic/go-ucfg/unpack.go b/vendor/github.com/elastic/go-ucfg/unpack.go index da859700cba..4c54426f0e1 100644 --- a/vendor/github.com/elastic/go-ucfg/unpack.go +++ b/vendor/github.com/elastic/go-ucfg/unpack.go @@ -106,7 +106,38 @@ func implementsUnpacker(t reflect.Type) bool { } } - return false + if t.NumMethod() == 0 { + return false + } + + // test if object has 'Unpack' method + method, ok := t.MethodByName("Unpack") + if !ok { + + return false + } + + // check method input and output parameters to match the ConfigUnpacker interface: + // func (to *T) Unpack(cfg *TConfig) error + // with T being the method receiver (input paramter 0) + // and TConfig being the aliased config type to convert to (input parameter 1) + paramCountCheck := method.Type.NumIn() == 2 && method.Type.NumOut() == 1 + if !paramCountCheck { + return false + } + if !method.Type.Out(0).Implements(tError) { + // return variable is not compatible to `error` type + return false + } + + // method receiver is known, check config parameters being compatible + tIn := method.Type.In(1) + acceptsConfig := tConfig.ConvertibleTo(tIn) || tConfigPtr.ConvertibleTo(tIn) + if !acceptsConfig { + return false + } + + return true } func unpackWith(opts *options, v reflect.Value, with value) Error { @@ -114,7 +145,8 @@ func unpackWith(opts *options, v reflect.Value, with value) Error { meta := with.meta() var err error - switch u := v.Interface().(type) { + value := v.Interface() + switch u := value.(type) { case Unpacker: var reified interface{} if reified, err = with.reify(opts); err == nil { @@ -156,6 +188,13 @@ func unpackWith(opts *options, v reflect.Value, with value) Error { if c, err = with.toConfig(opts); err == nil { err = u.Unpack(c) } + + default: + var c *Config + if c, err = with.toConfig(opts); err == nil { + err = reflectUnpackWithConfig(v, c) + } + } if err != nil { @@ -163,3 +202,23 @@ func unpackWith(opts *options, v reflect.Value, with value) Error { } return nil } + +func reflectUnpackWithConfig(v reflect.Value, c *Config) error { + method, _ := v.Type().MethodByName("Unpack") + tIn := method.Type.In(1) + + var rc reflect.Value + switch { + case tConfig.ConvertibleTo(tIn): + rc = reflect.ValueOf(*c) + case tConfigPtr.ConvertibleTo(tIn): + rc = reflect.ValueOf(c) + } + + results := method.Func.Call([]reflect.Value{v, rc.Convert(tIn)}) + ifc := results[0].Convert(tError).Interface() + if ifc == nil { + return nil + } + return ifc.(error) +} diff --git a/vendor/github.com/elastic/go-ucfg/util.go b/vendor/github.com/elastic/go-ucfg/util.go index 664317ecb7a..b7d71782ca2 100644 --- a/vendor/github.com/elastic/go-ucfg/util.go +++ b/vendor/github.com/elastic/go-ucfg/util.go @@ -7,6 +7,7 @@ import ( type tagOptions struct { squash bool + ignore bool } var noTagOpts = tagOptions{} @@ -18,6 +19,8 @@ func parseTags(tag string) (string, tagOptions) { switch opt { case "squash", "inline": opts.squash = true + case "ignore": + opts.ignore = true } } return s[0], opts From a8bd4160c9d447c0af0e230aa7493eba4ce99f9e Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 16:11:59 +0100 Subject: [PATCH 71/78] Improve couchbase module (#3545) * Fix typo spacial to spatial. Change fieldnames couchbase.node.couch.*.actual_disk_size.* to couchbase.node.couch.*.disk_size.* for consistency. * Add system tests to verify docs * Update data.json --- CHANGELOG.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 4 +- metricbeat/metricbeat.template-es2x.json | 8 +- metricbeat/metricbeat.template.json | 8 +- .../module/couchbase/bucket/_meta/data.json | 6 +- .../module/couchbase/cluster/_meta/data.json | 16 ++-- .../module/couchbase/node/_meta/data.json | 38 +++++----- .../module/couchbase/node/_meta/fields.yml | 4 +- metricbeat/module/couchbase/node/data.go | 6 +- metricbeat/module/couchbase/node/node_test.go | 18 ++--- metricbeat/module/couchbase/testing.go | 7 +- metricbeat/tests/system/test_couchbase.py | 75 +++++++++++++++++++ 12 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 metricbeat/tests/system/test_couchbase.py diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 58d0e139ef7..5919ae27c8f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -24,6 +24,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Linux cgroup metrics are now enabled by default for the system process metricset. The configuration option for the feature was renamed from `cgroups` to `process.cgroups.enabled`. {pull}3519[3519] +- Change fieldnames couchbase.node.couch.*.actual_disk_size.* to couchbase.node.couch.*.disk_size.* {pull}3545[3545] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 89ce3efe740..97dbdb2ff53 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1060,7 +1060,7 @@ Number of get commands [float] -=== couchbase.node.couch.docs.actual_disk_size.bytes +=== couchbase.node.couch.docs.disk_size.bytes type: long @@ -1096,7 +1096,7 @@ couchSpatialDiskSize field [float] -=== couchbase.node.couch.views.actual_disk_size.bytes +=== couchbase.node.couch.views.disk_size.bytes type: long diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 03cdfebdf0d..bf7b50bf72b 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -589,14 +589,14 @@ "properties": { "docs": { "properties": { - "actual_disk_size": { + "data_size": { "properties": { "bytes": { "type": "long" } } }, - "data_size": { + "disk_size": { "properties": { "bytes": { "type": "long" @@ -625,14 +625,14 @@ }, "views": { "properties": { - "actual_disk_size": { + "data_size": { "properties": { "bytes": { "type": "long" } } }, - "data_size": { + "disk_size": { "properties": { "bytes": { "type": "long" diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 316dae56668..eaa515f51b9 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -584,14 +584,14 @@ "properties": { "docs": { "properties": { - "actual_disk_size": { + "data_size": { "properties": { "bytes": { "type": "long" } } }, - "data_size": { + "disk_size": { "properties": { "bytes": { "type": "long" @@ -620,14 +620,14 @@ }, "views": { "properties": { - "actual_disk_size": { + "data_size": { "properties": { "bytes": { "type": "long" } } }, - "data_size": { + "disk_size": { "properties": { "bytes": { "type": "long" diff --git a/metricbeat/module/couchbase/bucket/_meta/data.json b/metricbeat/module/couchbase/bucket/_meta/data.json index 16f70befbbe..bab5ccb47a7 100644 --- a/metricbeat/module/couchbase/bucket/_meta/data.json +++ b/metricbeat/module/couchbase/bucket/_meta/data.json @@ -8,13 +8,13 @@ "bucket": { "data": { "used": { - "bytes": 12597798 + "bytes": 12585517 } }, "disk": { "fetches": 0, "used": { - "bytes": 16368995 + "bytes": 16356812 } }, "item_count": 7303, @@ -36,7 +36,7 @@ } }, "metricset": { - "host": "couchbase:8091", + "host": "localhost:8091", "module": "couchbase", "name": "bucket", "rtt": 115 diff --git a/metricbeat/module/couchbase/cluster/_meta/data.json b/metricbeat/module/couchbase/cluster/_meta/data.json index f090375706a..764ddff37d7 100644 --- a/metricbeat/module/couchbase/cluster/_meta/data.json +++ b/metricbeat/module/couchbase/cluster/_meta/data.json @@ -8,22 +8,22 @@ "cluster": { "hdd": { "free": { - "bytes": 50705599693 + "bytes": 49181251462 }, "quota": { "total": { - "bytes": 63381999616 + "bytes": 67371577344 } }, "total": { - "bytes": 63381999616 + "bytes": 67371577344 }, "used": { "by_data": { - "bytes": 16368995 + "bytes": 16356812 }, "value": { - "bytes": 12676399923 + "bytes": 18190325882 } } }, @@ -56,21 +56,21 @@ } }, "total": { - "bytes": 8360542208 + "bytes": 2096275456 }, "used": { "by_data": { "bytes": 53962160 }, "value": { - "bytes": 7660535808 + "bytes": 1235513344 } } } } }, "metricset": { - "host": "couchbase:8091", + "host": "localhost:8091", "module": "couchbase", "name": "cluster", "rtt": 115 diff --git a/metricbeat/module/couchbase/node/_meta/data.json b/metricbeat/module/couchbase/node/_meta/data.json index 0511a104e8e..e9158740302 100644 --- a/metricbeat/module/couchbase/node/_meta/data.json +++ b/metricbeat/module/couchbase/node/_meta/data.json @@ -9,14 +9,14 @@ "cmd_get": 0, "couch": { "docs": { - "actual_disk_size": { - "bytes": 13575997 - }, "data_size": { - "bytes": 9804800 + "bytes": 9796608 + }, + "disk_size": { + "bytes": 13567903 } }, - "spacial": { + "spatial": { "data_size": { "bytes": 0 }, @@ -25,16 +25,16 @@ } }, "views": { - "actual_disk_size": { - "bytes": 2792998 - }, "data_size": { - "bytes": 2792998 + "bytes": 2788909 + }, + "disk_size": { + "bytes": 2788909 } } }, "cpu_utilization_rate": { - "pct": 87.08791 + "pct": 4.568527918781726 }, "current_items": { "total": 7303, @@ -42,21 +42,21 @@ }, "ep_bg_fetched": 0, "get_hits": 0, - "hostname": "172.22.0.9:8091", + "hostname": "172.20.0.2:8091", "mcd_memory": { "allocated": { - "bytes": 6378 + "bytes": 1599 }, "reserved": { - "bytes": 6378 + "bytes": 1599 } }, "memory": { "free": { - "bytes": 2273726464 + "bytes": 1435561984 }, "total": { - "bytes": 8360542208 + "bytes": 2096275456 }, "used": { "bytes": 53962160 @@ -65,20 +65,20 @@ "ops": 0, "swap": { "total": { - "bytes": 4189057024 + "bytes": 0 }, "used": { - "bytes": 58044416 + "bytes": 0 } }, "uptime": { - "sec": 50492 + "sec": 891 }, "vb_replica_curr_items": 0 } }, "metricset": { - "host": "couchbase:8091", + "host": "localhost:8091", "module": "couchbase", "name": "node", "rtt": 115 diff --git a/metricbeat/module/couchbase/node/_meta/fields.yml b/metricbeat/module/couchbase/node/_meta/fields.yml index 77ba5719d5c..c444cf64229 100644 --- a/metricbeat/module/couchbase/node/_meta/fields.yml +++ b/metricbeat/module/couchbase/node/_meta/fields.yml @@ -9,7 +9,7 @@ type: long description: > Number of get commands - - name: couch.docs.actual_disk_size.bytes + - name: couch.docs.disk_size.bytes format: bytes type: long description: > @@ -27,7 +27,7 @@ type: long description: > couchSpatialDiskSize field - - name: couch.views.actual_disk_size.bytes + - name: couch.views.disk_size.bytes type: long description: > Amount of disk space occupied by Couch views (bytes) diff --git a/metricbeat/module/couchbase/node/data.go b/metricbeat/module/couchbase/node/data.go index c1d34e165ce..e57744ae8ec 100644 --- a/metricbeat/module/couchbase/node/data.go +++ b/metricbeat/module/couchbase/node/data.go @@ -73,14 +73,14 @@ func eventsMapping(content []byte) []common.MapStr { "cmd_get": NodeItem.InterestingStats.CmdGet, "couch": common.MapStr{ "docs": common.MapStr{ - "actual_disk_size": common.MapStr{ + "disk_size": common.MapStr{ "bytes": NodeItem.InterestingStats.CouchDocsActualDiskSize, }, "data_size": common.MapStr{ "bytes": NodeItem.InterestingStats.CouchDocsDataSize, }, }, - "spacial": common.MapStr{ + "spatial": common.MapStr{ "data_size": common.MapStr{ "bytes": NodeItem.InterestingStats.CouchSpatialDataSize, }, @@ -89,7 +89,7 @@ func eventsMapping(content []byte) []common.MapStr { }, }, "views": common.MapStr{ - "actual_disk_size": common.MapStr{ + "disk_size": common.MapStr{ "bytes": NodeItem.InterestingStats.CouchViewsActualDiskSize, }, "data_size": common.MapStr{ diff --git a/metricbeat/module/couchbase/node/node_test.go b/metricbeat/module/couchbase/node/node_test.go index 22f7ba874d4..21f05922c29 100644 --- a/metricbeat/module/couchbase/node/node_test.go +++ b/metricbeat/module/couchbase/node/node_test.go @@ -46,22 +46,22 @@ func TestFetchEventContents(t *testing.T) { couch := event["couch"].(common.MapStr) couch_docs := couch["docs"].(common.MapStr) - couch_docs_actual_disk_size := couch_docs["actual_disk_size"].(common.MapStr) - assert.EqualValues(t, 13563791, couch_docs_actual_disk_size["bytes"]) + couch_docs_disk_size := couch_docs["disk_size"].(common.MapStr) + assert.EqualValues(t, 13563791, couch_docs_disk_size["bytes"]) couch_docs_data_size := couch_docs["data_size"].(common.MapStr) assert.EqualValues(t, 9792512, couch_docs_data_size["bytes"]) - couch_spacial := couch["spacial"].(common.MapStr) - couch_spacial_data_size := couch_spacial["data_size"].(common.MapStr) - assert.EqualValues(t, 0, couch_spacial_data_size["bytes"]) + couch_spatial := couch["spatial"].(common.MapStr) + couch_spatial_data_size := couch_spatial["data_size"].(common.MapStr) + assert.EqualValues(t, 0, couch_spatial_data_size["bytes"]) - couch_spacial_disk_size := couch_spacial["disk_size"].(common.MapStr) - assert.EqualValues(t, 0, couch_spacial_disk_size["bytes"]) + couch_spatial_disk_size := couch_spatial["disk_size"].(common.MapStr) + assert.EqualValues(t, 0, couch_spatial_disk_size["bytes"]) couch_views := couch["views"].(common.MapStr) - couch_views_actual_disk_size := couch_views["actual_disk_size"].(common.MapStr) - assert.EqualValues(t, 2805219, couch_views_actual_disk_size["bytes"]) + couch_views_disk_size := couch_views["disk_size"].(common.MapStr) + assert.EqualValues(t, 2805219, couch_views_disk_size["bytes"]) couch_views_data_size := couch_views["data_size"].(common.MapStr) assert.EqualValues(t, 2805219, couch_views_data_size["bytes"]) diff --git a/metricbeat/module/couchbase/testing.go b/metricbeat/module/couchbase/testing.go index f91981e6cf8..d3b28ca0945 100644 --- a/metricbeat/module/couchbase/testing.go +++ b/metricbeat/module/couchbase/testing.go @@ -3,5 +3,10 @@ package couchbase import "os" func GetEnvDSN() string { - return os.Getenv("COUCHBASE_DSN") + dsn := os.Getenv("COUCHBASE_DSN") + + if len(dsn) == 0 { + dsn = "http://Administrator:password@localhost:8091" + } + return dsn } diff --git a/metricbeat/tests/system/test_couchbase.py b/metricbeat/tests/system/test_couchbase.py new file mode 100644 index 00000000000..d46bedb2823 --- /dev/null +++ b/metricbeat/tests/system/test_couchbase.py @@ -0,0 +1,75 @@ +import os +import metricbeat +import unittest + + +class Test(metricbeat.BaseTest): + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_bucket(self): + """ + couchbase bucket metricset test + """ + self.render_config_template(modules=[{ + "name": "couchbase", + "metricsets": ["bucket"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_cluster(self): + """ + couchbase cluster metricset test + """ + self.render_config_template(modules=[{ + "name": "couchbase", + "metricsets": ["cluster"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + def test_node(self): + """ + couchbase node metricset test + """ + self.render_config_template(modules=[{ + "name": "couchbase", + "metricsets": ["node"], + "hosts": self.get_hosts(), + "period": "1s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0, max_timeout=20) + proc.check_kill_and_wait() + + output = self.read_output_json() + self.assertTrue(len(output) >= 1) + evt = output[0] + print evt + + self.assert_fields_are_documented(evt) + + def get_hosts(self): + return [os.getenv('COUCHBASE_HOST', 'localhost') + ':' + + os.getenv('COUCHBASE_PORT', '8091')] From 06e70d2f9a9c6082a83d61f5b4ac7d10b1913df0 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 16:53:33 +0100 Subject: [PATCH 72/78] Fix php-fpm docker image (#3552) It seems in the most recent version of the Docker image the config path seems to have changed. --- metricbeat/module/php_fpm/_meta/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metricbeat/module/php_fpm/_meta/Dockerfile b/metricbeat/module/php_fpm/_meta/Dockerfile index 783a3db96dc..30265e2a339 100644 --- a/metricbeat/module/php_fpm/_meta/Dockerfile +++ b/metricbeat/module/php_fpm/_meta/Dockerfile @@ -1,6 +1,6 @@ FROM richarvey/nginx-php-fpm -RUN echo "pm.status_path = /status" >> /etc/php7/php-fpm.d/www.conf +RUN echo "pm.status_path = /status" >> /usr/local/etc/php-fpm.d/www.conf ADD ./php-fpm.conf /etc/nginx/sites-enabled EXPOSE 81 From 32d4285512bc34028e0606eebc34af158d8ac0da Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Tue, 7 Feb 2017 11:13:20 -0500 Subject: [PATCH 73/78] Configuration files must not be writeable by other users (#3544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Configuration files must not be writeable by other users … This PR adds enforcement of ownership and file permissions on configuration files. Any configuration file must be owned by the same user that the Beat is running as and the file must not be writable by anyone other than the owner. This strict permission checking is limited to platforms with POSIX file permissions. The DACLs used by Windows are not checked at this time. The check can be disabled on the CLI with `-strict.perms=false` or by setting env var `BEAT_STRICT_PERMS=false`. * Update jenkins_ci to fix umask on git clone --- .travis.yml | 2 + CHANGELOG.asciidoc | 1 + dev-tools/jenkins_ci | 2 + filebeat/fileset/modules_integration_test.go | 13 ++-- libbeat/cfgfile/cfgfile.go | 2 +- libbeat/cfgfile/cfgfile_test.go | 5 +- libbeat/common/config.go | 52 ++++++++++++++ libbeat/common/config_test.go | 38 ++++++++++ libbeat/common/file/fileinfo.go | 49 +++++++++++++ libbeat/common/file/fileinfo_test.go | 73 ++++++++++++++++++++ libbeat/common/file/fileinfo_unix.go | 25 +++++++ libbeat/common/file/fileinfo_windows.go | 14 ++++ libbeat/scripts/Makefile | 3 +- 13 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 libbeat/common/file/fileinfo.go create mode 100644 libbeat/common/file/fileinfo_test.go create mode 100644 libbeat/common/file/fileinfo_unix.go create mode 100644 libbeat/common/file/fileinfo_windows.go diff --git a/.travis.yml b/.travis.yml index f875a84ccc2..8206a90ebd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,8 @@ addons: - geoip-database before_install: + - umask 022 + - chmod -R go-w $GOPATH/src/github.com/elastic/beats # Docker-compose installation - sudo rm /usr/local/bin/docker-compose || true - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5919ae27c8f..59a40a438fa 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -15,6 +15,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Affecting all Beats* - Change beat generator. Use `$GOPATH/src/github.com/elastic/beats/script/generate.py` to generate a beat. {pull}3452[3452] +- Configuration files must not be writable by other users. {pull}3544[3544] *Filebeat* diff --git a/dev-tools/jenkins_ci b/dev-tools/jenkins_ci index f25a368a527..fb5afaa858b 100755 --- a/dev-tools/jenkins_ci +++ b/dev-tools/jenkins_ci @@ -131,6 +131,7 @@ main() { err "--build and --cleanup cannot be used together" exit 1 elif [ "$BUILD" == "true" ]; then + chmod -R go-w "${GOPATH}/src/github.com/elastic/beats" build elif [ "$CLEANUP" == "true" ]; then cleanup @@ -140,4 +141,5 @@ main() { fi } +umask 022 main $* diff --git a/filebeat/fileset/modules_integration_test.go b/filebeat/fileset/modules_integration_test.go index 8e98a2c848d..eb7237a07ad 100644 --- a/filebeat/fileset/modules_integration_test.go +++ b/filebeat/fileset/modules_integration_test.go @@ -45,8 +45,9 @@ func TestLoadPipeline(t *testing.T) { var res map[string]interface{} err = json.Unmarshal(response, &res) - assert.NoError(t, err) - assert.Equal(t, "describe pipeline", res["my-pipeline-id"].(map[string]interface{})["description"], string(response)) + if assert.NoError(t, err) { + assert.Equal(t, "describe pipeline", res["my-pipeline-id"].(map[string]interface{})["description"], string(response)) + } } func TestSetupNginx(t *testing.T) { @@ -62,10 +63,14 @@ func TestSetupNginx(t *testing.T) { } reg, err := newModuleRegistry(modulesPath, configs, nil, "5.2.0") - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } err = reg.LoadPipelines(client) - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } status, _, _ := client.Request("GET", "/_ingest/pipeline/filebeat-5.2.0-nginx-access-with_plugins", "", nil, nil) assert.Equal(t, 200, status) diff --git a/libbeat/cfgfile/cfgfile.go b/libbeat/cfgfile/cfgfile.go index cf8290846cf..d7eeaf1c0d4 100644 --- a/libbeat/cfgfile/cfgfile.go +++ b/libbeat/cfgfile/cfgfile.go @@ -91,7 +91,7 @@ func HandleFlags() error { func Read(out interface{}, path string) error { config, err := Load(path) if err != nil { - return nil + return err } return config.Unpack(out) diff --git a/libbeat/cfgfile/cfgfile_test.go b/libbeat/cfgfile/cfgfile_test.go index a8d1832345b..74d104df99a 100644 --- a/libbeat/cfgfile/cfgfile_test.go +++ b/libbeat/cfgfile/cfgfile_test.go @@ -34,8 +34,9 @@ func TestRead(t *testing.T) { config := &TestConfig{} - err = Read(config, absPath+"/config.yml") - assert.Nil(t, err) + if err = Read(config, absPath+"/config.yml"); err != nil { + t.Fatal(err) + } // validate assert.Equal(t, "localhost", config.Output.Elasticsearch.Host) diff --git a/libbeat/common/config.go b/libbeat/common/config.go index 593471acbdf..a35888da239 100644 --- a/libbeat/common/config.go +++ b/libbeat/common/config.go @@ -5,8 +5,11 @@ import ( "errors" "flag" "fmt" + "os" + "runtime" "strings" + "github.com/elastic/beats/libbeat/common/file" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/go-ucfg" "github.com/elastic/go-ucfg/cfgutil" @@ -14,6 +17,17 @@ import ( "github.com/elastic/go-ucfg/yaml" ) +var flagStrictPerms = flag.Bool("strict.perms", true, "Strict permission checking on config files") + +// IsStrictPerms returns true if strict permission checking on config files is +// enabled. +func IsStrictPerms() bool { + if !*flagStrictPerms || os.Getenv("BEAT_STRICT_PERMS") == "false" { + return false + } + return true +} + // Config object to store hierarchical configurations into. // See https://godoc.org/github.com/elastic/go-ucfg#Config type Config ucfg.Config @@ -143,6 +157,12 @@ func NewFlagOverwrite( } func LoadFile(path string) (*Config, error) { + if IsStrictPerms() { + if err := ownerHasExclusiveWritePerms(path); err != nil { + return nil, err + } + } + c, err := yaml.NewConfigWithFile(path, configOpts...) if err != nil { return nil, err @@ -390,3 +410,35 @@ func filterDebugObject(c interface{}) { } } } + +// ownerHasExclusiveWritePerms asserts that the current user is the +// owner of the config file and that the config file is (at most) writable by +// the owner (e.g. group and other cannot have write access). +func ownerHasExclusiveWritePerms(name string) error { + if runtime.GOOS == "windows" { + return nil + } + + info, err := file.Stat(name) + if err != nil { + return err + } + + euid := os.Geteuid() + fileUID, _ := info.UID() + perm := info.Mode().Perm() + + if euid != fileUID { + return fmt.Errorf(`config file ("%v") must be owned by the beat user `+ + `(uid=%v)`, name, euid) + } + + // Test if group or other have write permissions. + if perm&0022 > 0 { + return fmt.Errorf(`config file ("%v") can only be writable by the `+ + `owner but the permissions are "%v"`, + name, perm) + } + + return nil +} diff --git a/libbeat/common/config_test.go b/libbeat/common/config_test.go index 1d4e9db7ca3..6e9f2a3cfae 100644 --- a/libbeat/common/config_test.go +++ b/libbeat/common/config_test.go @@ -2,6 +2,9 @@ package common import ( "fmt" + "io/ioutil" + "os" + "runtime" "strings" "testing" @@ -110,3 +113,38 @@ func TestConfigPrintDebug(t *testing.T) { assert.Equal(t, test.expected, buf) } } + +func TestConfigFilePermissions(t *testing.T) { + if !IsStrictPerms() { + t.Skip("Skipping test because strict.perms is disabled") + } + + f, err := ioutil.TempFile("", "writableConfig.yml") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + + f.WriteString(`test.data: [1, 2, 3, 4]`) + f.Sync() + + if _, err = LoadFile(f.Name()); err != nil { + t.Fatal(err) + } + + // Permissions checking isn't implemented for Windows DACLs. + if runtime.GOOS == "windows" { + return + } + + if err = os.Chmod(f.Name(), 0460); err != nil { + t.Fatal(err) + } + + // Read will fail because config is group writable. + _, err = LoadFile(f.Name()) + if assert.Error(t, err, "expected writable error") { + assert.Contains(t, err.Error(), "writable") + } +} diff --git a/libbeat/common/file/fileinfo.go b/libbeat/common/file/fileinfo.go new file mode 100644 index 00000000000..05e9f2d2888 --- /dev/null +++ b/libbeat/common/file/fileinfo.go @@ -0,0 +1,49 @@ +package file + +import ( + "errors" + "os" +) + +// A FileInfo describes a file and is returned by Stat and Lstat. +type FileInfo interface { + os.FileInfo + UID() (int, error) // UID of the file owner. Returns an error on non-POSIX file systems. + GID() (int, error) // GID of the file owner. Returns an error on non-POSIX file systems. +} + +// Stat returns a FileInfo describing the named file. +// If there is an error, it will be of type *PathError. +func Stat(name string) (FileInfo, error) { + return stat(name, os.Stat) +} + +// Lstat returns a FileInfo describing the named file. +// If the file is a symbolic link, the returned FileInfo +// describes the symbolic link. Lstat makes no attempt to follow the link. +// If there is an error, it will be of type *PathError. +func Lstat(name string) (FileInfo, error) { + return stat(name, os.Lstat) +} + +type fileInfo struct { + os.FileInfo + uid *int + gid *int +} + +func (f fileInfo) UID() (int, error) { + if f.uid == nil { + return -1, errors.New("uid not implemented") + } + + return *f.uid, nil +} + +func (f fileInfo) GID() (int, error) { + if f.gid == nil { + return -1, errors.New("gid not implemented") + } + + return *f.gid, nil +} diff --git a/libbeat/common/file/fileinfo_test.go b/libbeat/common/file/fileinfo_test.go new file mode 100644 index 00000000000..53aecf6bb93 --- /dev/null +++ b/libbeat/common/file/fileinfo_test.go @@ -0,0 +1,73 @@ +// +build !windows + +package file_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/elastic/beats/libbeat/common/file" + "github.com/stretchr/testify/assert" +) + +func TestStat(t *testing.T) { + f, err := ioutil.TempFile("", "teststat") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + link := filepath.Join(os.TempDir(), "teststat-link") + if err := os.Symlink(f.Name(), link); err != nil { + t.Fatal(err) + } + defer os.Remove(link) + + info, err := file.Stat(link) + if err != nil { + t.Fatal(err) + } + + assert.True(t, info.Mode().IsRegular()) + + uid, err := info.UID() + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, os.Geteuid(), uid) + + gid, err := info.GID() + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, os.Getegid(), gid) +} + +func TestLstat(t *testing.T) { + link := filepath.Join(os.TempDir(), "link") + if err := os.Symlink("dummy", link); err != nil { + t.Fatal(err) + } + defer os.Remove(link) + + info, err := file.Lstat(link) + if err != nil { + t.Fatal(err) + } + + assert.True(t, info.Mode()&os.ModeSymlink > 0) + + uid, err := info.UID() + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, os.Geteuid(), uid) + + gid, err := info.GID() + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, os.Getegid(), gid) +} diff --git a/libbeat/common/file/fileinfo_unix.go b/libbeat/common/file/fileinfo_unix.go new file mode 100644 index 00000000000..68d60b1cb83 --- /dev/null +++ b/libbeat/common/file/fileinfo_unix.go @@ -0,0 +1,25 @@ +// +build !windows + +package file + +import ( + "errors" + "os" + "syscall" +) + +func stat(name string, statFunc func(name string) (os.FileInfo, error)) (FileInfo, error) { + info, err := statFunc(name) + if err != nil { + return nil, err + } + + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return nil, errors.New("failed to get uid/gid") + } + + uid := int(stat.Uid) + gid := int(stat.Gid) + return fileInfo{FileInfo: info, uid: &uid, gid: &gid}, nil +} diff --git a/libbeat/common/file/fileinfo_windows.go b/libbeat/common/file/fileinfo_windows.go new file mode 100644 index 00000000000..e6ae54ae35e --- /dev/null +++ b/libbeat/common/file/fileinfo_windows.go @@ -0,0 +1,14 @@ +package file + +import ( + "os" +) + +func stat(name string, statFunc func(name string) (os.FileInfo, error)) (FileInfo, error) { + info, err := statFunc(name) + if err != nil { + return nil, err + } + + return fileInfo{FileInfo: info}, nil +} diff --git a/libbeat/scripts/Makefile b/libbeat/scripts/Makefile index fdadc5d447a..37024f74544 100755 --- a/libbeat/scripts/Makefile +++ b/libbeat/scripts/Makefile @@ -301,7 +301,8 @@ stop-environment: .PHONY: write-environment write-environment: mkdir -p ${BUILD_DIR} - echo "ES_HOST=${ES_HOST}" > ${BUILD_DIR}/test.env + echo "BEAT_STRICT_PERMS=false" > ${BUILD_DIR}/test.env + echo "ES_HOST=${ES_HOST}" >> ${BUILD_DIR}/test.env echo "ES_PORT=9200" >> ${BUILD_DIR}/test.env echo "ES_USER=beats" >> ${BUILD_DIR}/test.env echo "ES_PASS=testing" >> ${BUILD_DIR}/test.env From cd5dc8191ca64535fbc25734d1b3808012611bc0 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 17:15:11 +0100 Subject: [PATCH 74/78] Always use absolute path for event and registry (#3328) During some testing with filebeat I realised that when a relative path glob is put into the filebeat config, the event will contain the relative path and also the state. In most cases this should not be an issue and so far no issues were reported. For the state itself it is not an issue as they are compared based on inode/device. It could become an issue on restart in case a config was changed from a relative to an absolute path and the prospector does not detect, that the state would belong to the same prospector. This could also have an affect when migrating to this solutions. Old states could be left over in the registry file. But this requires, that someone was using relative paths before which was never recommended. --- CHANGELOG.asciidoc | 3 ++- filebeat/_meta/fields.common.yml | 2 +- filebeat/docs/fields.asciidoc | 2 +- filebeat/prospector/prospector_log.go | 5 +++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 59a40a438fa..036f7be965b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -18,6 +18,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Configuration files must not be writable by other users. {pull}3544[3544] *Filebeat* +- Always use absolute path for event and registry. This can lead to issues when relative paths were used before. {pull}3328[3328] *Heartbeat* @@ -39,6 +40,7 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] - Add `_id`, `_type`, `_index` and `_score` fields in the generated index pattern. {pull}3282[3282] *Filebeat* +- Always use absolute path for event and registry. {pull}3328[3328] *Heartbeat* @@ -48,7 +50,6 @@ https://github.com/elastic/beats/compare/v5.1.1...master[Check the HEAD diff] *Packetbeat* - *Winlogbeat* diff --git a/filebeat/_meta/fields.common.yml b/filebeat/_meta/fields.common.yml index 54a37b7c2ee..c34200224a1 100644 --- a/filebeat/_meta/fields.common.yml +++ b/filebeat/_meta/fields.common.yml @@ -7,7 +7,7 @@ type: keyword required: true description: > - The file from which the line was read. This field contains the full path to the file. + The file from which the line was read. This field contains the absolute path to the file. For example: `/var/log/system.log`. - name: offset diff --git a/filebeat/docs/fields.asciidoc b/filebeat/docs/fields.asciidoc index d933b336314..069e3b10d44 100644 --- a/filebeat/docs/fields.asciidoc +++ b/filebeat/docs/fields.asciidoc @@ -403,7 +403,7 @@ type: keyword required: True -The file from which the line was read. This field contains the full path to the file. For example: `/var/log/system.log`. +The file from which the line was read. This field contains the absolute path to the file. For example: `/var/log/system.log`. [float] diff --git a/filebeat/prospector/prospector_log.go b/filebeat/prospector/prospector_log.go index ff4b62a5c61..c255763f3a7 100644 --- a/filebeat/prospector/prospector_log.go +++ b/filebeat/prospector/prospector_log.go @@ -215,6 +215,11 @@ func (p *ProspectorLog) scan() { default: } + var err error + path, err = filepath.Abs(path) + if err != nil { + logp.Err("could not fetch abs path for file %s: %s", path, err) + } logp.Debug("prospector", "Check file for harvesting: %s", path) // Create new state for comparison From 360a3dac70037c54044433a2cb38e8bd161191ca Mon Sep 17 00:00:00 2001 From: Monica Sarbu Date: Tue, 7 Feb 2017 17:34:35 +0100 Subject: [PATCH 75/78] Remove exclude pattern from visualizations (#3548) --- .../_meta/kibana/search/NFS-errors-search.json | 16 ++++++++++++++++ .../_meta/kibana/visualization/NFS-errors.json | 4 ++-- .../_meta/kibana/visualization/Event-Levels.json | 6 +++--- .../_meta/kibana/visualization/Sources.json | 6 +++--- 4 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 packetbeat/_meta/kibana/search/NFS-errors-search.json diff --git a/packetbeat/_meta/kibana/search/NFS-errors-search.json b/packetbeat/_meta/kibana/search/NFS-errors-search.json new file mode 100644 index 00000000000..f34a6ca3efc --- /dev/null +++ b/packetbeat/_meta/kibana/search/NFS-errors-search.json @@ -0,0 +1,16 @@ +{ + "sort": [ + "@timestamp", + "desc" + ], + "hits": 0, + "description": "", + "title": "NFS errors search", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"packetbeat-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[{\"meta\":{\"negate\":false,\"index\":\"packetbeat-*\",\"key\":\"type\",\"value\":\"nfs\",\"disabled\":false,\"alias\":null},\"query\":{\"match\":{\"type\":{\"query\":\"nfs\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}},{\"meta\":{\"negate\":true,\"index\":\"packetbeat-*\",\"key\":\"nfs.status\",\"value\":\"NFSERR_NOENT\",\"disabled\":false,\"alias\":null},\"query\":{\"match\":{\"nfs.status\":{\"query\":\"NFSERR_NOENT\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}},{\"meta\":{\"negate\":true,\"index\":\"packetbeat-*\",\"key\":\"nfs.status\",\"value\":\"NFS_OK\",\"disabled\":false,\"alias\":null},\"query\":{\"match\":{\"nfs.status\":{\"query\":\"NFS_OK\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}" + }, + "columns": [ + "_source" + ] +} \ No newline at end of file diff --git a/packetbeat/_meta/kibana/visualization/NFS-errors.json b/packetbeat/_meta/kibana/visualization/NFS-errors.json index 041376c965e..1ad752d6969 100644 --- a/packetbeat/_meta/kibana/visualization/NFS-errors.json +++ b/packetbeat/_meta/kibana/visualization/NFS-errors.json @@ -1,10 +1,10 @@ { - "visState": "{\"title\":\"NFS errors\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":true,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"overlap\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"nfs.status\",\"exclude\":{\"pattern\":\"NFS_OK|NFSERR_NOENT\"},\"size\":12,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "visState": "{\"title\":\"NFS errors\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"nfs.status\",\"size\":12,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", "description": "", "title": "NFS errors", "uiStateJSON": "{}", "version": 1, - "savedSearchId": "nfs", + "savedSearchId": "NFS-errors-search", "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[]}" } diff --git a/winlogbeat/_meta/kibana/visualization/Event-Levels.json b/winlogbeat/_meta/kibana/visualization/Event-Levels.json index 36faf385b23..85edcdbf615 100644 --- a/winlogbeat/_meta/kibana/visualization/Event-Levels.json +++ b/winlogbeat/_meta/kibana/visualization/Event-Levels.json @@ -1,10 +1,10 @@ { - "visState": "{\"type\":\"table\",\"params\":{\"perPage\":5,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"level\",\"exclude\":{\"pattern\":\"\\\"\\\"\"},\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "visState": "{\"title\":\"Event Levels\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"level\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", "description": "", "title": "Event Levels", - "uiStateJSON": "{}", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", "version": 1, "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"winlogbeat-*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}" + "searchSourceJSON": "{\"index\":\"winlogbeat-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" } } \ No newline at end of file diff --git a/winlogbeat/_meta/kibana/visualization/Sources.json b/winlogbeat/_meta/kibana/visualization/Sources.json index 5e50e0f8e59..b80ae7fc55d 100644 --- a/winlogbeat/_meta/kibana/visualization/Sources.json +++ b/winlogbeat/_meta/kibana/visualization/Sources.json @@ -1,10 +1,10 @@ { - "visState": "{\n \"type\": \"pie\",\n \"params\": {\n \"shareYAxis\": true,\n \"addTooltip\": true,\n \"addLegend\": true,\n \"isDonut\": false\n },\n \"aggs\": [\n {\n \"id\": \"1\",\n \"type\": \"count\",\n \"schema\": \"metric\",\n \"params\": {}\n },\n {\n \"id\": \"2\",\n \"type\": \"terms\",\n \"schema\": \"segment\",\n \"params\": {\n \"field\": \"source_name\",\n \"exclude\": {\n \"pattern\": \"\\\"\\\"\"\n },\n \"size\": 7,\n \"order\": \"desc\",\n \"orderBy\": \"1\"\n }\n }\n ],\n \"listeners\": {}\n}", + "visState": "{\"title\":\"Sources\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"source_name\",\"size\":7,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", "description": "", - "title": "Event Sources", + "title": "Sources", "uiStateJSON": "{}", "version": 1, "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\n \"index\": \"winlogbeat-*\",\n \"query\": {\n \"query_string\": {\n \"query\": \"*\",\n \"analyze_wildcard\": true\n }\n },\n \"filter\": []\n}" + "searchSourceJSON": "{\"index\":\"winlogbeat-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" } } \ No newline at end of file From f6698a5d60fdfc3d90859e9765a7308de0831eda Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Tue, 7 Feb 2017 17:55:49 +0100 Subject: [PATCH 76/78] Update docker image with data.json (#3523) * Add docs for labels and tags --- metricbeat/docs/fields.asciidoc | 32 +++++++++++++++ metricbeat/metricbeat.template-es2x.json | 12 ++++++ metricbeat/metricbeat.template.json | 12 ++++++ .../module/docker/container/_meta/fields.yml | 10 +++++ .../module/docker/image/_meta/data.json | 41 ++++++++++++------- .../module/docker/image/_meta/fields.yml | 16 +++++--- 6 files changed, 103 insertions(+), 20 deletions(-) diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 97dbdb2ff53..d1b9a43be27 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1334,6 +1334,22 @@ type: long Size of the files that have been created or changed since creation. +[float] +=== docker.container.labels + +type: dict + +Image labels. + + +[float] +=== docker.container.tags + +type: list + +Image tags. + + [float] == cpu Fields @@ -1557,6 +1573,22 @@ type: long Total size of the all cached images associated to the current image. +[float] +=== docker.image.labels + +type: dict + +Image labels. + + +[float] +=== docker.image.tags + +type: list + +Image tags. + + [float] == info Fields diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index bf7b50bf72b..f1e8e629905 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -771,6 +771,9 @@ "index": "not_analyzed", "type": "string" }, + "labels": { + "properties": {} + }, "name": { "ignore_above": 1024, "index": "not_analyzed", @@ -790,6 +793,9 @@ "ignore_above": 1024, "index": "not_analyzed", "type": "string" + }, + "tags": { + "properties": {} } } }, @@ -896,6 +902,9 @@ } } }, + "labels": { + "properties": {} + }, "size": { "properties": { "regular": { @@ -905,6 +914,9 @@ "type": "long" } } + }, + "tags": { + "properties": {} } } }, diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index eaa515f51b9..4ca248c165c 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -763,6 +763,9 @@ "ignore_above": 1024, "type": "keyword" }, + "labels": { + "properties": {} + }, "name": { "ignore_above": 1024, "type": "keyword" @@ -780,6 +783,9 @@ "status": { "ignore_above": 1024, "type": "keyword" + }, + "tags": { + "properties": {} } } }, @@ -889,6 +895,9 @@ } } }, + "labels": { + "properties": {} + }, "size": { "properties": { "regular": { @@ -898,6 +907,9 @@ "type": "long" } } + }, + "tags": { + "properties": {} } } }, diff --git a/metricbeat/module/docker/container/_meta/fields.yml b/metricbeat/module/docker/container/_meta/fields.yml index cf56b0a142f..4735ce82e54 100644 --- a/metricbeat/module/docker/container/_meta/fields.yml +++ b/metricbeat/module/docker/container/_meta/fields.yml @@ -40,3 +40,13 @@ type: long description: > Size of the files that have been created or changed since creation. + - name: labels + type: dict + dict-type: keyword + description: > + Image labels. + - name: tags + type: list + dict-type: keyword + description: > + Image tags. diff --git a/metricbeat/module/docker/image/_meta/data.json b/metricbeat/module/docker/image/_meta/data.json index 0c3fadc2f75..c2655f7641c 100644 --- a/metricbeat/module/docker/image/_meta/data.json +++ b/metricbeat/module/docker/image/_meta/data.json @@ -1,19 +1,30 @@ { - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" }, - "metricset":{ - "host":"localhost", - "module":"docker", - "name":"image", - "rtt":44269 - }, - "docker":{ - "image":{ - "example": "image" + "docker": { + "image": { + "created": "2017-01-30T23:22:04.000Z", + "id": { + "current": "sha256:67c7d2208cddecd2135be7bbb10769952eb27407085f8a5d44e177c0c9caec78", + "parent": "sha256:eeb83c4591a16ade6d97b9175b2fbb015a9933c644170dda762af3ccfe09dbb3" + }, + "size": { + "regular": 484864857, + "virtual": 484864857 + }, + "tags": [ + "kibana:latest" + ] } }, - "type":"metricsets" -} + "metricset": { + "host": "unix:///var/run/docker.sock", + "module": "docker", + "name": "image", + "rtt": 115 + }, + "type": "metricsets" +} \ No newline at end of file diff --git a/metricbeat/module/docker/image/_meta/fields.yml b/metricbeat/module/docker/image/_meta/fields.yml index c4bb774a38f..19f71e358bd 100644 --- a/metricbeat/module/docker/image/_meta/fields.yml +++ b/metricbeat/module/docker/image/_meta/fields.yml @@ -34,8 +34,14 @@ description: > Total size of the all cached images associated to the current image. -# TODO : How to describe tags & labels list ? -# - name: tags -# type: list ? -# description: > -# Descriptive or given name(s) to the image. + - name: labels + type: dict + dict-type: keyword + description: > + Image labels. + + - name: tags + type: list + dict-type: keyword + description: > + Image tags. From 60e799937d48a67359b70edb2586b8c36ab6b961 Mon Sep 17 00:00:00 2001 From: Monica Sarbu Date: Tue, 7 Feb 2017 22:10:05 +0100 Subject: [PATCH 77/78] Add dashboards to the list of configuration options (#3551) --- winlogbeat/config/config.go | 1 + winlogbeat/config/config_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/winlogbeat/config/config.go b/winlogbeat/config/config.go index c73cf52d869..736dcd83814 100644 --- a/winlogbeat/config/config.go +++ b/winlogbeat/config/config.go @@ -49,6 +49,7 @@ func (s Settings) Validate() error { "name", "refresh_topology_freq", "topology_expire", "geoip", "queue_size", "bulk_queue_size", "max_procs", "processors", "logging", "output", "path", "winlogbeat", + "dashboards", } sort.Strings(validKeys) diff --git a/winlogbeat/config/config_test.go b/winlogbeat/config/config_test.go index 0c39a261038..2291bdb290f 100644 --- a/winlogbeat/config/config_test.go +++ b/winlogbeat/config/config_test.go @@ -44,7 +44,7 @@ func TestConfigValidate(t *testing.T) { }, map[string]interface{}{"other": "value"}, }, - "1 error: Invalid top-level key 'other' found. Valid keys are bulk_queue_size, " + + "1 error: Invalid top-level key 'other' found. Valid keys are bulk_queue_size, dashboards, " + "fields, fields_under_root, geoip, logging, max_procs, " + "name, output, path, processors, queue_size, refresh_topology_freq, tags, topology_expire, winlogbeat", }, From 0575fef12f76a6c386da670e7415beb433eb6479 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 7 Feb 2017 22:34:14 +0100 Subject: [PATCH 78/78] version fix --- libbeat/docs/version.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbeat/docs/version.asciidoc b/libbeat/docs/version.asciidoc index 83dc47ce8ec..e07df0beaf9 100644 --- a/libbeat/docs/version.asciidoc +++ b/libbeat/docs/version.asciidoc @@ -1,3 +1,3 @@ -:stack-version: 5.1.0 +:stack-version: 5.3.0 :doc-branch: 5.x :go-version: 1.7.4

jF*x11PO;`8h%y-g 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≻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 E4>0czga7~l diff --git a/packetbeat/docs/images/kibana-navigation-vis.png b/packetbeat/docs/images/kibana-navigation-vis.png index d80f35056bc35a5eeceb4d4973a73873bf214c50..71b87ffe8dc4b71a0d8f0ae523949fe795610d88 100644 GIT binary patch literal 77206 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

NAFwnTr97y-;2*5A9(}<99`H= zcvSuH@U?&T0(HojBkcPV9cXgzf5YVdM@ae+-s`66m*a7j-!U`&%2q%QaMF;GHW1Aq z0}YY2EMN4}PfYOsh6*1V`C+uPU<;HgLcZ@Np9pR{^XLZur`JEgp9+4Ltz_0TVU+=9 zKrvA_dgaOWF`*ij%8(K(+W+P6Hz4@M-*+9N!EraP{4ZemKWKZ=H=u~Z9j3(ByXtem z(pNnBscz%{0yA|$-gwulCZA<(+PAAJJrItt_#fw(!$d4)phw>RErb7;>L2a_Km7U) z1%%DX8Eb0Le2YjCqP61>Ux~I;1%@UZ{C6+YOXk*MJSe=LG6QiS0DD?=NWMEFY2l?_ycyMH4Z5dVOjEzQe!MOcwo z>VK4pb|M%6bx5+XxGlXHs#tB;Bth_xu33um_bR}b;w@B}LMFHW1MvM3FF}K-9J5D! z*CeAYK~?;%!SXC1*eq-iDpp7(E?|qAf0g>byY0Fa$+~JNoyWl7(E1Pc{0C8WaeUMt zGK2{9{+qy`($;?p3Jv9Z@hjTb*!=%hgI>hI0G7_>>}9ozk2L5~&J^tZP0 zJl*?)vO6*&h}7ff9o=#UL9+1=qW{V!pqft+Fa9W9Etw=m{;%UKq6Ll;4FnyGHdvp^ zoh)J5SZ9$TjmLr{=TjpSt1|~cnbtNhX-HNTTjs{&{Ncwr#!t00RQ7&7I$Hxs713q%G0e zS(^-x{DIHASfu&IK;1hlw1e(>^&rt6zfoLrDlL)5y#>F`BtfK@Wrcp6{-FD0z{G2~ag+w{uzIwDADVqYA01gx%j0=wBKQ zq%}O7H1u^gJq=YH>SYkj@@VizvA;6POGLaKRY!yNw@EY&K-s^)EJVvJ)Nca;>gjoM z2)QZI!E%fgX7{kbVE2B1II9hPtsuIQ)CF%SS;D zKPw==2}ogz%%DZuPaRDqydaIq?WyDW;t%v1RBR8{I2z*SDlScdba1#2St52sxwXKqIhE|5TK9HmR_hUHC+H zI>gD%@RcBvzAO3&)#r&t+0K5|uvF}pS%v?2BbaP|thd}-tGBNkwTO1x530dN^62}O z(z{dxMRBitdwB;yH(Q|Q_~vND;*SU@iMwvkI=r9hO~)|D(l}6l{``4^OF1nWTxFb% z^pFg-0H(}y+sQ6Jn<)KWp4&#{ZW`D4J8t?|rKJ64C2O!(pE^|@Y_?r~utXnB`_g+j zTLULHW2%0lXCdzyU|BbMJ?GM&;>dZ9V#}3FPO4iIe*B62xgKNreJyGs{<3PJIajW; zyD8fS#hAEXf7nyX{t4hv_F_qK)XJGVv5~s5eZ(KN_}KJq=MX}bb1pM=PQUkwPeFe= zOQw&3BLX|=;MFTYjH-RiTE{`vT1>CkkduJTNr(dFop9BN>~!Hw>)UT$V$9*+6pPI$bLu}@%?1b}FD`d3TGRbnbM1wV=4K(IU*V# zI^4_)dfpd{&vkXOg{%gPtrOc}`+EeVmSX9*@jo~(k=t4gQOeoKbGq5i+r#L5*I??q zfhC@O2Z@(_uQz9gxV=A-$>zsPYkmuQ!2A-y;SZ8~NVu-x5s2XX-tlRotqlm=631~S zhrwpr?JBh;BU+$CD}w3rF0%T^-3SQkk@}xakytF~zPJ(r5kwb{ zIT)7@H*DWk`l82=SIUXNKLW=M5D?CiFRVV71fZxM{5laUb44Qt*S%|XnLs^& z5xLo_uR!eX?*h_?`%n%5f-P!pZf1A8$f~LTg;*jZoX%(h6BieUN(J~hdFOO6xf!n2 zrINEy6Nc18`@8rEis!R-JC$bub>!}*&)@iYL#c8N&6BpD@V2FGb|>#+C2jJ7M(r)S z>-t*R8H!5-GIYiCxl-nHgbAKcq`_;8AWkh>CiS}CC0$#fSg13Zv}xOWPp`EZM+3SV*tTBTGW}BpqGlSM2h;C)qG=$vraJt&PTG1d&q7BxPmjx`JEK~w;m^Eo zIKD`8M?qu&ZyeUBn(>W!dlD>#xD&9r0NHk43ls@)8D93x%dbmBbf*hm3<1E|lM*2K zJR!a6HbC86FheHLxe*x!MF4(u^jckjm|6Xwx~?;-sij>*gcA^? z_ui3?f>MMKA=E&mD@{>dXJPqL=PZcxt=gmu679R5ND6(qU=SWH0^7!YN($+=Js*Hd2WU|!uIfL_Ot@z5%L|SFg|sV8 zNxo9qU(r7kq4scH1mdp!V0u&nyWB!$57*}iYLM2JY0dX)2kJstW@&8kTTf*=CqlF~ z3|YyBn8}s+ekQq@23x)k&C+>NB>C z@5WBLk2Sob42V!x&A8K%6(OW9Ezd!JBS=egf9nU6kS(t^BbyIv0JEyf1?@C~qoX~L zf21@_>X))Iv2gTkOh-!qPaa&F6HPM3=k);GBfD2)O2^kAZw!7^6X3HdN8}bj5!c-1 zv3Elg^^%=AmF`5p0B$}JPI6x5BsS_rVSu6qGMc5PJ5H$x8gnBTddPa`E*9Gd@z z`vr&6NRFO6ox!*O(tK`lg&L?yU$xqqFq!t9GdqSyPAu4bhcv!3PhRGAXR<1UTRXHV zkVS%q*Cb5rk~~aNJ9M43j0t0*zEGCUJfEy7!4mvU-v>TR#Wi#&ZO~$ScT5HbYWj|c z)56~c>~6a7GK*~8XrAk^2+-%$*Ge=lzW6wIj;TqCcqwywh=WYCJbAYm36YIx$yauT zGL;C-S7kd>+|UuxwkyEPYT17&QpA7yA=zsg{I_A39eIUKlP}Kny;=1ihlhuLXPfn@ zYaJ36b)7ZvF^_euh+RlV>{J1iIiOzrqf1s5K~@Tz5^m=cwMaVRp&jq)BX^UO+_y-u zBAm3e_W*%##6s$>mAI#oA*ey~KNH!a#9@sG|evm^)mq3$%kYw2^R-WT5XwScRZuh2qEMMd&5s5eMp4dPj~hR_12E#jXk_Hu4HH1X@t_q+K;%!yt?+h zt54_PeV~@~crzQ_Q*B2~#ZXm>Nfg14ulJWpNhDO=tUnPa`LK{= zR?%UI!|oofff%{zU3x&RRy#qrDOf5NSNw+;^$K{{v7$|-s#l`t0ef)KkvPSHKOv9I zM9octlt(ZcmbzN=#cCvvzJOf&E`fEU7?E_n=akZH5MxU^#S%&InqKH&m)ZLCD zq%|wmR;M1SR-W6vrIM%!+xqD6WHEUMhJTS9RmED6cAg4`w^R zV7HAsCSn@qq!6Z)H>ASw9<#fbWQZl-kKQC7W*}Gu&~@t5umY?5$6MUODPwD-x48R+lb(FLh`7n;45c@Nufx;Pi2w~o zcJH0vJ=r$a3ubiBCSP*Lkznz1_JVU-s#Q-1ONBakPVw10`_shu*n!wsA+D_pLJ4S+KafQR1L$f1^Bpy19pWQyuZQT*)4Z;V7N{S*0qd2 z$llC>|9=0LiJ6h_t|(j7Gv59Q!mh;hj#*Gr+AOM)f(R(AD3x$sp(O6uuWv=GFW2CU z_wur`SpQreN1s(FIby4WWki%^HfUccc-lWQ+>Md<-fv0-{(GWN)m$y9M1!BZ86RPR zTJh!>Sa=?92Sd69P(@UCKJoF<(sIOypR3xTdQ}(TZaS2S*|4I3+)iZXFy>R!Y989& z`5Y<$1>JQ|3JZY<@2^jAVlwU)Y&`!mWRoHxQpQRR?$%+~uSm!-*0aO`KOAY$P0t!$xtk^$f2)WLrJ|2RiXB*@4 zxSMzc70AOff%CupWxhIZ|2&?b^l!M_nx4OZp`qOmSsV!vFt4(KM_EJIBY{&-x;wuf zN1p!)x>&Fj*t(>Es{E$3a%%=A1oic&UN`7O!qc7!wS{%n#Di$CrNUV+HiAE%Uf>$` zj@)hLiod}%loU7peVmpe_E&xVw))@|^1#mn+DVbe^2c}a5y2QX{ejZ^mlv+vr@M_T zb<6DgEb<(STTbPVXkTUPxeLC0=;Z*;E9&k8KA)Mmwg<{gnx_5@fVv57w0txe?n^FQ zGV<0lFEh0FoxDAEu|!c@jO~99;j-t(XmptqFCphTitKsZrYMze*^{WMMLg*r02hAc zq5xR|o_nErS}6y95bc_fVHE(P0m=1GKldl?tPP*hGA$%6oa??-GztY~rIR{fNOLm- zb7w|t{amnQBtYO?quh$vHMuS(=gESBA3SJ12=lnyn^D znQfT%rQaDjc}+6Pa_l!O_B){;zCh@$HA}4fuf4Xu+Lyyb=_}$&T+&W|cn{CBoLIaF zv#gI5bV@bor&7%!7_Evm{nAUn*%5EMNIz1gZ7YbchKuK2guQ>uVQ2pg!?{hzS~7)E z^6MkULpS(eMu7{+Z!)nkYA&;OFN(W8JR!OM-R_;qd7e%JXgF(i_qlmLSs(jSx*&)o$ z@kdSC)5`vCSWh1Sx7sK0XgmN?=LSr;MasIwmg9+HHhWu|oW=4|ImS_kRL|e_XC*yy zY_WIkNZV&^;#wB$kIo(rjq-6;y-(K|#3brYbz!mUI$4ag37LY!SvO*8;lrI$ z!6$;(?jg1clfS<3GP2QJCNBy#xzN$ah&~1p9CZnrzIY;w?<)n#foL^@ULG;yRGo;q zieIs0M@1dX^rW~kK9OHERGj(kK?scQRHVJb^ByJ zEdahPsdi@Dox}iBLE+Xc^d0R`ff5Q6pMfnTel&_s{66$o|a;867 zZrm((6uhrvP_boovw@z}mo{E3SO49Bp$@a}8uwgPlqz#=n7Ly|!u5hy8`VrC1-XBk zrx0uQY@>|#3d~+tNaic+30LfwAA~$A4xluS7>6PM#LdG&nA`8;)St)xqvat%<8}T) z5~O|?OXXILK7+8tV26AMP4Ro0g;=V<<`GU3npH#Mg0l29LNL?e=Huky&;hh+0W<{d z)0{!F|NLmoz221%txcj)o4hg~)S5E7$8dSt!+CE#8*{3xQo1fF99tTs0Ges)YUN;s z6&5yW!yg}*<(9(FEt?#M55 zR+6{erfywU3^2LQBIS3PKWKU0*jA20S#F-}aJ5~kuCX-=o^e=N8=na_?*^6OjX+Gx zPUF$Wdozf0Hno@&ATddkX--d%T!QA;_xn?oIw$F4Pa0cx*O!Bs0{eRuhj~(1aH5OJ zAv;R=>N%q)UpC0C-PH5V;g);1%Ic+EZJA+{5eeDT70408uzB>E*rq+ z7wf3yDiXuyLp}8#T5!%H$t&kur#f zc2W1n$S5w9dH?E0q^#ngyVat4&BkgbJW!e~?rEppD zDt%>LZL>$8R7njH+OH1M#^T2Dc|Zj70dH%?>OENvsLi5n?XYq`JD@q0?YdW_U3Dry zNL!o=BEGe^aC)$1fq(P@$i*89uBW(3y;o+r8OzjGDvdC{5u?tHP2n$G7wSRhO<@y5 zO$6fw8{dYA$l81cYHYm0yH7Q~9Co>!gPnyev?k1Q)z@ZIO~=jiW>A+=luG{M1rUY~ zh61KSk>NxD{9`cj1TcwfddwjW1@!HAKn@QLN{Gcy4b9MNxfThz@`e&!HMlA&bO6^Y zf<1qQyB;v`Ek56x3PGdm5TAt+7wd9}@6S;eHjC-~;wL8SfI+^0TH$rUVPk8PlO7=$ zA#D6ky%pKJR#(rhnE!Sf+Kwba)}eH!XZU+uvM-I92ZJSw*h)Y{wj?xQB4R?3Gfnb} zspPRidcw?2ck`ApD3>!KnH00B2@lFVszdFcYJ|Fq<~cCYl*BEmOtrsS5{h(&M{?3M zhd9UGDml}KG`=nM(@wmosZySA{QnH`@7ew55j+66?zc5n`BUgNYsR2`kkRmNU6z=8 z)2hPSw}YeUoO+9pa@_8;5US~gnA3k}`G2}-7bSO$sF%pNJGsN3J=qe%9sRc0e6(%U zFe^^6!MnM>dD8qRI1#+A6GCtKFC6|C5h8#qqkhGn8g(I;a>L|QRrl<*%In2-Y>@Gn z0;s78>pKbm`Y8XJ1y~HQNfuD&b*b^~P*$~_W!{6uRFgRR10~O2ie}AhzOUqIt)^-Y7_oHbO%mT diff --git a/metricbeat/docs/images/kibana-navigation-vis.png b/metricbeat/docs/images/kibana-navigation-vis.png index 5dc39464fbff57600c13a892ed16e278b3936695..df4dcd5ee26c7a308e1147a42f3a7ec39d8601f2 100644 GIT binary patch literal 92059 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%0AEi`#7bjz=z3;S{eYCNP>FShJN5O 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⪙pkRyHN;1_an49ER(rh0JWX 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 C#y&^@ literal 106385 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 diff --git a/packetbeat/docs/images/kibana-created-indexes.png b/packetbeat/docs/images/kibana-created-indexes.png index 1794991593500cde0493af7e578ee7999aba9f13..343675b05e325675a58bb184496b2695cc48590f 100644 GIT binary patch literal 58659 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