diff --git a/plugins/inputs/procstat/like2regexp/like2regexp.go b/plugins/inputs/procstat/like2regexp/like2regexp.go new file mode 100644 index 0000000000000..21083341a4741 --- /dev/null +++ b/plugins/inputs/procstat/like2regexp/like2regexp.go @@ -0,0 +1,40 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package like2regexp + +import ( + "strings" +) + +const special = `\.+*?()|{}^$` // All regexp special chars excetp `[]` + +func WMILikeToRegexp(like string) string { + var buf strings.Builder + // Quote special characters + inclass := false + for i := 0; i < len(like); i++ { + c := like[i] + + if inclass && c == ']' { + inclass = false + } else if c == '[' { + inclass = true + } else if !inclass { + switch c { + case '_': + c = '.' + case '%': + buf.WriteByte('.') + c = '*' + default: + if strings.IndexByte(special, c) != -1 { // Escape special chars + buf.WriteByte('\\') + } + } + } + buf.WriteByte(c) + } + + return `(?i:^` + buf.String() + `$)` +} \ No newline at end of file diff --git a/plugins/inputs/procstat/like2regexp/like2regexp_test.go b/plugins/inputs/procstat/like2regexp/like2regexp_test.go new file mode 100644 index 0000000000000..b97d529e57f5a --- /dev/null +++ b/plugins/inputs/procstat/like2regexp/like2regexp_test.go @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package like2regexp + +import ( + "regexp" + "testing" +) + +var tests = []struct { + input, expected string + matches, excludes []string +}{ + {`abc`, `(?i:^abc$)`, + []string{`abc`, `ABC`, `AbC`}, + []string{`def`, `DEF`}, + }, + {`a.c`, `(?i:^a\.c$)`, + []string{`a.c`}, + []string{`abc`, `abbc`, `xcc`, `abb`, `a`, `ac`}, + }, + {`%watch%`, `(?i:^.*watch.*$)`, + []string{`amazon-cloudwatch-agent.exe`, `WatchSomething`, `watch`, `amazoncloudwatch`}, + []string{`amazon-ssm-agent.exe`, `wach`, `amazoncloudwatc`}, + }, + {`[a-z]`, `(?i:^[a-z]$)`, + []string{`a`, `c`, `x`}, + []string{`1`, `2`, `3`, `ab`}, + }, + {`[^a-z]`, `(?i:^[^a-z]$)`, + []string{`1`, `2`}, + []string{`a`, `c`, `x`, `abc`}, + }, + {`abc^def[^a-z]`, `(?i:^abc\^def[^a-z]$)`, + []string{`abc^def1`, `abc^def_`}, + []string{`abc^defa`, `abc^defz`, `abcdef`}, + }, + {`a_c`, `(?i:^a.c$)`, + []string{`a.c`, `abc`, `acc`}, + []string{`bbc`, `abb`, `ac`}, + }, + {`%a_c%`, `(?i:^.*a.c.*$)`, + []string{`aa.cc`, `abc`, `acc`, `xxxabcxxx`}, + []string{`abbc`, `abbb`, `bbac`}, + }, + {`%[_][_]%`, `(?i:^.*[_][_].*$)`, + []string{`__`, `a__cc`}, + []string{`_x_`, `acc`}, + }, + {`he[[]ll]o.exe`, `(?i:^he[[]ll]o\.exe$)`, + []string{`he[ll]o.exe`}, + []string{`hello.exe`}, + }, +} + +func TestWMILikeToRegexp(t *testing.T) { + for _, test := range tests { + output := WMILikeToRegexp(test.input) + if test.expected != output { + t.Errorf("translated regexp does not match expected result:\n\tExpected: %v\n\tOutput : %v", test.expected, output) + } + + re := regexp.MustCompile(output) + for _, m := range test.matches { + if !re.MatchString(m) { + t.Errorf("case '%v': generated regexp %v does not match value '%v'", test.input, output, m) + } + } + for _, m := range test.excludes { + if re.MatchString(m) { + t.Errorf("case '%v': generated regexp %v should not match value '%v'", test.input, output, m) + } + } + } +} \ No newline at end of file diff --git a/plugins/inputs/procstat/native_finder_windows.go b/plugins/inputs/procstat/native_finder_windows.go index 6dcc0575af258..fee173ee425c7 100644 --- a/plugins/inputs/procstat/native_finder_windows.go +++ b/plugins/inputs/procstat/native_finder_windows.go @@ -28,3 +28,51 @@ func (pg *NativeFinder) Pattern(pattern string) ([]PID, error) { } return pids, err } + +var patternCache = make(map[string]*regexp.Regexp) +var pcmut sync.RWMutex + +func likeToRegexp(p string) (*regexp.Regexp, error) { + pcmut.RLock() + re, ok := patternCache[p] + pcmut.RUnlock() + if ok { + return re, nil + } + + pattern := like2regexp.WMILikeToRegexp(p) + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + pcmut.Lock() + patternCache[p] = re + pcmut.Unlock() + return re, nil +} + +//FullPattern matches on the command line when the process was executed +func (pg *NativeFinder) FullPattern(pattern string) ([]PID, error) { + var pids []PID + + regxPattern, err := likeToRegexp(pattern) + if err != nil { + return pids, err + } + procs, err := process.Processes() + if err != nil { + return pids, err + } + for _, p := range procs { + cmd, err := p.Cmdline() + if err != nil { + //skip, this can be caused by the pid no longer existing + //or you having no permissions to access it + continue + } + if regxPattern.MatchString(cmd) { + pids = append(pids, PID(p.Pid)) + } + } + return pids, err +} \ No newline at end of file