ATLAS Offline Software
Loading...
Searching...
No Matches
dlldep.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
4#
5# @file: dlldep.py
6#
7# @purpose: Create a graph of the runtime dependencies of a dynamic library using ldd.
8# The dependencies are printed in DOT (graphviz) language.
9#
10# @author: Frank Winklmeier (CERN)
11# based on Dominik Seichter's 'dependencies.sh':
12# http://domseichter.blogspot.com/2008/02/visualize-dependencies-of-binaries-and.html
13#
14
15import sys
16from os.path import basename
17import subprocess as sp
18import re
19
20import PyUtils.Dso as Dso
21
22class Cache:
23 """Global cache of already processed files"""
24
25 files = {} # Global cache of already processed libs
26 stats = [] # Global statistics
27
28 def __init__(self):
29 self.myfiles = {} # Dependencies of the currently processed lib
30 self.dotsrc = [] # DOT source code
31
32 def add(self, shlib):
33 """Add file to local and global cache"""
34 self.myfiles[shlib.lib] = shlib
35 Cache.files[shlib.lib] = shlib
36
37 def writeDOT(self, file):
38 for d in self.dotsrc: print (file, d, file=file)
39
40 def dot(self, code, style={}):
41 """Output a line of dot code"""
42 if len(style)>0:
43 code += ' ['
44 for k,v in style.items():
45 code += '%s="%s" ' % (k,v)
46 code += ']'
47
48 self.dotsrc.append(code)
49 return
50
51
53 """Represent a shared library with name, dependencies and other stats"""
54
55 def __init__(self, distance, lib):
56 import os.path as osp
57 lib = osp.expanduser(osp.expandvars(lib))
58 if not osp.exists(lib):
59 l = Dso.find_library(lib)
60 if l:
61 lib = l
62 assert osp.exists(lib), "no such path [%s]" % (lib,)
63 self.lib = lib # path of library
64 self.distance = distance # distance from root lib
65 self.deplibs = self._getLibs(lib) # direct dependencies
66
67 def _getLibs(self, lib):
68 """Get direct dependencies of shared library"""
69
70 # First check if already in global cache
71 cachedlib = Cache.files.get(lib)
72 if cachedlib: return cachedlib.deplibs
73
74 # Run readelf to find direct dependencies
75 # Note: ldd itself does recursions so we cannot use it here
76 encargs = {'encoding' : 'utf-8'}
77 p = sp.Popen(["readelf","-d",lib], stdout=sp.PIPE, **encargs)
78 output = p.communicate()[0]
79 if p.returncode != 0:
80 print ("Cannot run 'readelf' on",lib)
81 return []
82
83 libs = []
84 for l in output.split("\n"):
85 if l.find("NEEDED")==-1: continue
86 libs += [l.split()[-1].strip("[]")]
87
88 # Run ldd to find full path of libraries
89 p = sp.Popen(["ldd",lib], stdout=sp.PIPE, **encargs)
90 output = p.communicate()[0]
91 if p.returncode != 0:
92 print ("Cannot run 'ldd' on",lib)
93 return []
94
95 libpaths = []
96 for l in output.split("\n"):
97 fields = l.strip().split()
98 if len(fields)!=4: continue
99 path = fields[2]
100 if (fields[0] in libs) and len(path)>0:
101 libpaths += [path]
102
103 return libpaths
104
105
106class Color:
107 """Helper class for colored nodes"""
108
109 default = "white"
110 scheme = "rdbu8"
111 projects = {"DetCommon" : 2,
112 "AtlasCore" : 1,
113 "AtlasEvent" : 3,
114 "AtlasConditions" : 4,
115 "AtlasReconstruction" : 5,
116 "AtlasSimulation" : 6,
117 "AtlasTrigger" : 7,
118 "AtlasAnalysis" : 8
119 }
120
121 @classmethod
122 def get(cls, lib):
123 for p,c in cls.projects.items():
124 if lib.find(p)!=-1: return "/%s/%s" % (cls.scheme, c)
125 return cls.default
126
127
128class Stats:
129 """Statistics calculated from Cache object"""
130
131 def __init__(self, lib, cache):
132 self.lib = lib
133 self.depTotal = len(cache.myfiles)-1 # do not count ourselves
134 self.depDirect = 0
135
136 for lib in cache.myfiles.values():
137 if lib.distance==1: self.depDirect += 1
138 return
139
140
141def anaLib(lib, opt, cache, select = [], ignore = [], depth = 0):
142 """Get dependencies of shared library recursively"""
143
144 def process(path):
145 """Should this lib be processed?"""
146 for regexp in select:
147 if regexp.match(path): return True
148 if len(select)>0: return False
149
150 for regexp in ignore:
151 if regexp.match(path): return False
152 return True
153
154 if opt.maxdepth and depth>=opt.maxdepth: return
155
156 # Check if we analyzed this lib already
157 cachedlib = cache.myfiles.get(lib)
158 if cachedlib:
159 # Always save minimum distance
160 if depth<cachedlib.distance: cachedlib.distance=depth
161 return
162
163 shlib = SharedLib(depth, lib)
164 cache.add(shlib)
165
166 for l in shlib.deplibs:
167 if process(l):
168 cache.dot(' "%s" -> "%s"' % (basename(lib), basename(l)))
169 anaLib(l, opt, cache, select, ignore, depth+1)
170
171 return
172
173
174def processLib(lib, opt, dotFileName = None):
175 """Process one library"""
176
177 cache = Cache()
178 dot = cache.dot # shortcut
179
180 dot('digraph DependencyTree {')
181 dot(' ratio=0.9 nodesep=0.05') # some reasonable default values
182 dot(' "%s" [shape=box]' % basename(lib))
183
184 select = []
185 ignore = [] # currently not used
186 if opt.filter:
187 for f in opt.filter: select += [re.compile(f)]
188 else:
189 select = [re.compile(".*atlas/software.*")]
190
191 anaLib(lib, opt, cache, select, ignore)
192
193 # Declare style of all nodes
194 for l,v in cache.myfiles.items():
195 style = {}
196 # Special style for direct dependencies
197 if v.distance==1:
198 style["shape"] = "box"
199
200 if not opt.nocolor:
201 style["style"] = "filled"
202 style["fillcolor"] = Color.get(l)
203
204 dot(' "%s"' % (basename(l)), style)
205
206 dot('}')
207
208 # Write output to file
209 if dotFileName: outFile = open(dotFileName, "w")
210 else: outFile = open(basename(lib)+".dot", "w")
211
212 cache.writeDOT(outFile)
213
214 # Calculate statistics
215 if opt.stats:
216 st = Stats(lib, cache)
217 Cache.stats += [st]
218 return st
219
220 return None
221
222
223
225 """Print statistics"""
226 import operator
227
228 print ("%-50s %7s %7s" % ("Library dependencies","Direct","Total"))
229 print ("-"*70)
230 for s in sorted(Cache.stats, key=operator.attrgetter("depDirect"), reverse=True):
231 print ("%-50s %7d %7d" % (basename(s.lib), s.depDirect, s.depTotal))
232
233 return
234
235
236def main():
237
238 import optparse
239 parser = optparse.OptionParser(description="Create runtime dependecy graph for shared library. The output is a graph in DOT language. To visualize it use, e.g. 'dot -O -Tps mygraph.dot'. The rectangular nodes represent direct dependencies. Nodes belonging to the same project have the same color.",
240 usage="%prog [OPTIONS] LIB [LIB...]")
241
242 parser.add_option("-o", "--output",
243 help="File for DOT source code (default is LIB.dot)")
244
245 parser.add_option("-d", "--maxdepth", type="int",
246 help="Maximum depth of dependency tree [1..]")
247
248 parser.add_option("-f", "--filter", action="append",
249 help="Only analyze libraries matching regular expression (can be specified multiple times) [default: .*atlas/software.*]")
250
251 parser.add_option("--nocolor", action="store_true",
252 help="Do not use colors")
253
254 parser.add_option("-s", "--stats", action="store_true",
255 help="Print statistics")
256
257 (opt, args) = parser.parse_args()
258 if len(args)==0:
259 parser.error("Invalid number of arguments specified")
260
261
262 if len(args)>1 and opt.output:
263 print ("Multiple libraries specified. Ignoring output file name.")
264 opt.output = None
265
266 for lib in args:
267 processLib(lib, opt, opt.output)
268
269 if opt.stats:
270 printStats()
271
272 return 0
273
274
275if __name__ == "__main__":
276 try:
277 sys.exit(main())
278 except KeyboardInterrupt:
279 sys.exit(1)
280
writeDOT(self, file)
Definition dlldep.py:37
list dotsrc
Definition dlldep.py:30
dict myfiles
Definition dlldep.py:29
add(self, shlib)
Definition dlldep.py:32
__init__(self)
Definition dlldep.py:28
dict projects
Definition dlldep.py:111
get(cls, lib)
Definition dlldep.py:122
str default
Definition dlldep.py:109
_getLibs(self, lib)
Definition dlldep.py:67
__init__(self, distance, lib)
Definition dlldep.py:55
int depDirect
Definition dlldep.py:134
int depTotal
Definition dlldep.py:133
__init__(self, lib, cache)
Definition dlldep.py:131
const std::string process
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
processLib(lib, opt, dotFileName=None)
Definition dlldep.py:174
printStats()
Definition dlldep.py:224
anaLib(lib, opt, cache, select=[], ignore=[], depth=0)
Definition dlldep.py:141
main()
Definition dlldep.py:236
Definition dot.py:1
std::string basename(std::string name)
Definition utils.cxx:207