Skip to content

Commit a4fcb38

Browse files
Move the functionality of ZenModel.OSProcessMatcher and ZenModel.OSProcessState into ZenRRD.
The ZenModel.OSProcessMatcher and ZenModel.OSProcessState modules are kept for compatibility with ZenPacks. This will aid in the Python 3 conversion. ZEN-35265
1 parent fe6e33d commit a4fcb38

File tree

12 files changed

+406
-340
lines changed

12 files changed

+406
-340
lines changed

src/Products/DataCollector/ProcessCommandPlugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#
88
##############################################################################
99

10-
from Products.ZenModel.OSProcessMatcher import buildObjectMapData
10+
from Products.ZenRRD.osprocess import buildObjectMapData
1111

1212
from .plugins.CollectorPlugin import CommandPlugin
1313

src/Products/DataCollector/plugins/zenoss/snmp/HRSWRunMap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
GetTableMap,
2929
SnmpPlugin,
3030
)
31-
from Products.ZenModel.OSProcessMatcher import buildObjectMapData
31+
from Products.ZenRRD.osprocess import buildObjectMapData
3232

3333
HRSWRUNENTRY = ".1.3.6.1.2.1.25.4.2.1"
3434

src/Products/ZenModel/OSProcess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from AccessControl import Permissions
1414
from Products.ZenModel.ZenossSecurity import *
1515
from Products.ZenModel.Lockable import Lockable
16-
from Products.ZenModel.OSProcessMatcher import OSProcessMatcher
16+
from Products.ZenRRD.osprocess import OSProcessMatcher
1717
from Commandable import Commandable
1818
from Products.ZenRelations.RelSchema import *
1919
from Products.ZenWidgets import messaging

src/Products/ZenModel/OSProcessClass.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from AccessControl import ClassSecurityInfo
1616
from AccessControl import Permissions
1717
from Products.ZenModel.ZenossSecurity import *
18-
from Products.ZenModel.OSProcessMatcher import OSProcessClassMatcher
18+
from Products.ZenRRD.osprocess import OSProcessClassMatcher
1919
from Commandable import Commandable
2020
from Products.ZenRelations.RelSchema import *
2121
from Products.ZenWidgets import messaging
Lines changed: 19 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -1,271 +1,19 @@
1-
##############################################################################
2-
#
3-
# Copyright (C) Zenoss, Inc. 2013, all rights reserved.
4-
#
5-
# This content is made available according to terms specified in
6-
# License.zenoss under the directory where your Zenoss product is installed.
7-
#
8-
##############################################################################
9-
10-
import logging
11-
import re
12-
import os
13-
import time
14-
import signal
15-
from contextlib import contextmanager
16-
from sre_parse import parse_template
17-
from md5 import md5
18-
19-
from Products.ZenUtils.Utils import prepId
20-
21-
log = logging.getLogger("zen.osprocessmatcher")
22-
23-
BLANK_PARSE_TEMPLATE = ([],[])
24-
25-
class OSProcessClassMatcher(object):
26-
"""
27-
Mixin class, for process command line matching functionality common to
28-
OSProcessClass and OSProcess.
29-
30-
Classes which mixin OSProcessClassMatcher must provide:
31-
self.includeRegex: string
32-
self.excludeRegex: string or None
33-
self.replaceRegex: string or None
34-
self.replacement: string or None
35-
self.processClassPrimaryUrlPath(): string
36-
"""
37-
38-
def matches(self, processText):
39-
"""
40-
Compare the process name and parameters.
41-
42-
@return: Does the process's command line match this process matcher?
43-
@rtype: Boolean
44-
"""
45-
if not processText: return False
46-
processText = processText.strip()
47-
if self._searchIncludeRegex(processText):
48-
return not self._searchExcludeRegex(processText)
49-
return False
50-
51-
def generateId(self, processText):
52-
"""
53-
Generate the unique ID of the OSProcess that the process belongs
54-
to, based on the given process's command line. Assumes that the
55-
processText has already passed the OSProcessClass's matches method.
56-
57-
The ID is based on a digest of the result of generateName, scoped below
58-
the OSProcessClass's primaryUrlPath.
59-
60-
(In order to get an "other" bucket, the replaceRegex should be written
61-
to match the entire string, and every capture group should be optional.)
62-
63-
@return: The unique ID of the corresponding OSProcess
64-
@rtype: string
65-
"""
66-
return self.generateIdFromName(self.generateName(processText))
67-
68-
def generateIdFromName(self, name):
69-
"""
70-
Generate the unique ID of the OSProcess that the process belongs
71-
to, based on the results of generateName(). Assumes that the
72-
processText has already passed the OSProcessClass's matches method,
73-
and that the name provided came from generateName(processText).
74-
75-
The ID is based on a digest of the name, scoped below the
76-
OSProcessClass's primaryUrlPath.
77-
78-
@return: The unique ID of the corresponding OSProcess
79-
@rtype: string
80-
"""
81-
generatedId = prepId(self.processClassPrimaryUrlPath()) + "_" + \
82-
md5(name).hexdigest().strip()
83-
log.debug("Generated unique ID: %s", generatedId)
84-
return generatedId
85-
86-
87-
def generateName(self, processText):
88-
"""
89-
Generate the name of an OSProcess.
90-
91-
Strips the processText of whitespace, applies the replacement
92-
(globally), and strips any remaining leading and trailing whitespace.
93-
94-
@return: The name of the corresponding OSProcess
95-
@rtype: string
96-
"""
97-
return self._applyReplacement(processText.strip()).strip()
98-
99-
def _applyReplacement(self, processText):
100-
regex = self._compiledRegex('replaceRegex')
101-
if regex:
102-
# We can't simply use re.sub, it blows up if the replacement
103-
# back-references an optional capture group that captured None.
104-
# In that case, we want it to replace the back-reference with ''.
105-
try:
106-
groups, literals = self._compiledReplacement('replaceRegex',
107-
'replacement')
108-
except Exception:
109-
log.warn("Invalid replacement rule on %s", self)
110-
return processText
111-
parts = []
112-
frontier = 0
113-
for match in regex.finditer(processText):
114-
if match.start() == match.end():
115-
continue
116-
parts.append(processText[frontier:match.start()])
117-
for index, group in groups:
118-
try:
119-
literal = match.group(group) or ''
120-
except IndexError as e:
121-
log.warn("Invalid replacement rule on %s", self)
122-
return processText
123-
literals[index] = literal
124-
parts.extend(literals)
125-
frontier = match.end()
126-
parts.append(processText[frontier:])
127-
return ''.join(parts)
128-
return processText
129-
130-
def _searchIncludeRegex(self, processText):
131-
r = self._compiledRegex('includeRegex')
132-
return r and r.search(processText)
133-
134-
def _searchExcludeRegex(self, processText):
135-
r = self._compiledRegex('excludeRegex')
136-
return r and r.search(processText)
137-
138-
def _compiledRegex(self, field):
139-
regex = getattr(self, field, None)
140-
if not regex: return None
141-
cache = self._compiledCache()
142-
if field in cache and regex in cache[field]:
143-
return cache[field][regex]
144-
else:
145-
try:
146-
compiled = re.compile(regex)
147-
cache[field] = {regex : compiled}
148-
return compiled
149-
except re.error as e:
150-
log.warn("Invalid %s on %s", field, self)
151-
cache[field] = {regex : None}
152-
return None
153-
154-
def _compiledReplacement(self, regexField, replField):
155-
repl = getattr(self, replField, None)
156-
if not repl: return BLANK_PARSE_TEMPLATE
157-
cache = self._compiledCache()
158-
regex = getattr(self, regexField, None)
159-
if replField in cache and (regex,repl) in cache[replField]:
160-
return cache[replField][(regex,repl)]
161-
else:
162-
try:
163-
compiled = parse_template(repl, self._compiledRegex(regexField))
164-
cache[replField] = {(regex,repl) : compiled}
165-
return compiled
166-
except Exception:
167-
log.warn("Invalid %s on %s", replField, self)
168-
cache[replField] = {(regex,repl) : None}
169-
return None
170-
171-
def _compiledCache(self):
172-
cache = getattr(self, '_compiled_cache', None)
173-
if not cache:
174-
cache = self._compiled_cache = {}
175-
return cache
176-
177-
class OSProcessMatcher(OSProcessClassMatcher):
178-
"""
179-
Mixin class, for process command line matching functionality in OSProcess.
180-
181-
Classes which mixin OSProcessMatcher must provide:
182-
self.includeRegex: string
183-
self.excludeRegex: string or None
184-
self.replaceRegex: string or None
185-
self.replacement: string or None
186-
self.processClassPrimaryUrlPath(): string
187-
self.generatedId: string
188-
"""
189-
def matches(self, processText):
190-
if super(OSProcessMatcher, self).matches(processText):
191-
generatedId = getattr(self,'generatedId',False)
192-
return self.generateId(processText) == generatedId
193-
return False
194-
195-
class DataHolder(object):
196-
def __init__(self, **attribs):
197-
for k,v in attribs.items():
198-
setattr(self,k,v)
199-
200-
def __repr__(self):
201-
return "<" + self.__class__.__name__ + ": " + str(self.__dict__) + ">"
202-
203-
def processClassPrimaryUrlPath(self):
204-
return self.primaryUrlPath
205-
206-
207-
class OSProcessClassDataMatcher(DataHolder, OSProcessClassMatcher):
208-
pass
209-
210-
class OSProcessDataMatcher(DataHolder, OSProcessMatcher):
211-
pass
212-
213-
def applyOSProcessClassMatchers(matchers, lines):
214-
"""
215-
@return (matched, unmatched), where...
216-
matched is: {matcher => {generatedName => [line, ...], ...}, ...}
217-
unmatched is: [line, ...]
218-
"""
219-
matched = {}
220-
unmatched = []
221-
for line in lines:
222-
log.debug("COMMAND LINE: %s", line)
223-
unmatchedLine = True
224-
for matcher in matchers:
225-
if matcher.matches(line):
226-
if matcher not in matched:
227-
matched[matcher] = {}
228-
generatedName = matcher.generateName(line)
229-
if generatedName not in matched[matcher]:
230-
matched[matcher][generatedName] = []
231-
matched[matcher][generatedName].append(line)
232-
unmatchedLine = False
233-
break
234-
if unmatchedLine:
235-
unmatched.append(line)
236-
return (matched, unmatched)
237-
238-
def applyOSProcessMatchers(matchers, lines):
239-
"""
240-
@return (matched, unmatched), where...
241-
matched is: {generatedName => [line, ...], ...}
242-
unmatched is: [line, ...]
243-
"""
244-
matched = {}
245-
unmatched = []
246-
for line in lines:
247-
log.debug("COMMAND LINE: %s", line)
248-
unmatchedLine = True
249-
for matcher in matchers:
250-
if matcher.matches(line):
251-
if matcher.generatedName not in matched:
252-
matched[matcher.generatedName] = []
253-
matched[matcher.generatedName].append(line)
254-
unmatchedLine = False
255-
break
256-
if unmatchedLine:
257-
unmatched.append(line)
258-
return (matched, unmatched)
259-
260-
def buildObjectMapData(processClassMatchData, lines):
261-
matchers = map(lambda(d):OSProcessClassDataMatcher(**d), processClassMatchData)
262-
matched, unmatched = applyOSProcessClassMatchers(matchers, lines)
263-
result = []
264-
for matcher, matchSet in matched.items():
265-
for name, matches in matchSet.items():
266-
result.append({
267-
'id': matcher.generateIdFromName(name),
268-
'displayName': name,
269-
'setOSProcessClass': matcher.primaryDmdId,
270-
'monitoredProcesses': matches})
271-
return result
1+
from Products.ZenRRD.osprocess import (
2+
OSProcessClassMatcher,
3+
OSProcessMatcher,
4+
OSProcessClassDataMatcher,
5+
OSProcessDataMatcher,
6+
applyOSProcessClassMatchers,
7+
applyOSProcessMatchers,
8+
buildObjectMapData,
9+
)
10+
11+
__all__ = (
12+
"OSProcessClassMatcher",
13+
"OSProcessMatcher",
14+
"OSProcessClassDataMatcher",
15+
"OSProcessDataMatcher",
16+
"applyOSProcessClassMatchers",
17+
"applyOSProcessMatchers",
18+
"buildObjectMapData",
19+
)
Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,3 @@
1-
##############################################################################
2-
#
3-
# Copyright (C) Zenoss, Inc. 2013, all rights reserved.
4-
#
5-
# This content is made available according to terms specified in
6-
# License.zenoss under the directory where your Zenoss product is installed.
7-
#
8-
##############################################################################
1+
from Products.ZenRRD.osprocess import determineProcessState
92

10-
def determineProcessState(beforeProcessSetPIDs, afterProcessSetPIDs):
11-
deadPids = set()
12-
restartedPids = set()
13-
newPids = set()
14-
15-
# if beforeProcessSetPIDs is empty, do nothing (beforeProcessSetPIDs is empty on the first run)
16-
if not beforeProcessSetPIDs:
17-
return (deadPids, restartedPids, newPids)
18-
19-
beforeProcessSets = set(beforeProcessSetPIDs.keys())
20-
afterProcessSets = set(afterProcessSetPIDs.keys())
21-
22-
beforeOnlyProcessSets = beforeProcessSets - afterProcessSets
23-
afterOnlyProcessSets = afterProcessSets - beforeProcessSets
24-
25-
beforeAndAfterProcessSets = beforeProcessSets.intersection(afterProcessSets)
26-
27-
for processSet in beforeOnlyProcessSets:
28-
deadPids.update(beforeProcessSetPIDs[processSet])
29-
30-
for processSet in afterOnlyProcessSets:
31-
newPids.update(afterProcessSetPIDs[processSet])
32-
33-
for processSet in beforeAndAfterProcessSets:
34-
beforePids = set(beforeProcessSetPIDs[processSet])
35-
afterPids = set(afterProcessSetPIDs[processSet])
36-
37-
oldUniquePids = beforePids - afterPids
38-
newUniquePids = afterPids - beforePids
39-
40-
if len(afterPids) <= len(beforePids):
41-
oldUniquePidsList = list(oldUniquePids)
42-
43-
# the difference of beforePids and afterPids is the number of deadPids
44-
numDead = len(beforePids) - len(afterPids)
45-
deadPids.update(oldUniquePidsList[0:numDead])
46-
47-
# all of the new unique pids are categorized as restarts
48-
restartedPids.update(newUniquePids)
49-
50-
elif len(afterPids) > len(beforePids):
51-
newUniquePidsList = list(newUniquePids)
52-
53-
# the difference of afterPids and beforePids is the number of newPids
54-
numNew = len(afterPids) - len(beforePids)
55-
newPids.update(newUniquePidsList[0:numNew])
56-
57-
# the remaining unique new pids are categorized as restarts
58-
restartedPids.update(newUniquePidsList[numNew:])
59-
60-
return (deadPids, restartedPids, newPids)
3+
__all__ = ("determineProcessState",)

0 commit comments

Comments
 (0)