Skip to content

Commit

Permalink
Merge pull request #5 from FrostyX/develop
Browse files Browse the repository at this point in the history
Version 0.2
  • Loading branch information
FrostyX committed Mar 9, 2014
2 parents eceafe4 + 8f44e82 commit 59a9142
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 133 deletions.
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ How does he do it? He simply finds all packages you have modified since you boot
- Python [psutil](https://code.google.com/p/psutil/) module. Available [here](https://admin.fedoraproject.org/pkgdb/acls/name/python-psutil) and [here](https://packages.gentoo.org/package/dev-python/psutil). Please use testing version on gentoo.

## Usage
### Basics
Clone tracer from GitHub

git clone [email protected]:FrostyX/tracer.git
Expand All @@ -20,6 +21,48 @@ And simply run it

_Yeah, you really have to run it as root._

### Symlink in PATH
Since there is no installation method so far, you can make symlink of `bin/tracer.py` to your `PATH` directory by yourself. Then you can use `tracer` just by calling its name.

# Make symlink
sudo ln -s tracer/bin/tracer.py /usr/local/bin/tracer

# And then just run
sudo tracer

### Arguments and pipes
You can also specify packages that *only* should be traced. Just pass them as arguments [1] or pipe them into `tracer` [2] \(or combine them\)

# [1]
sudo tracer mpd ncmpcpp vim

# [2]
echo mpd ncmpcpp vim | sudo tracer


## Integration with package managers
### DNF
Make symlink of `integration/dnf/plugins/tracer.py` file to your [dnf plugin directory](http://akozumpl.github.io/dnf/api_conf.html#dnf.conf.Conf.pluginpath)

Tracer is called after every successful transaction.

$[FrostyX ~]-> sudo dnf update vim-X11
...
Running transaction
Upgrading : vim-common-2:7.4.179-1.fc20.i686 1/6
Upgrading : vim-X11-2:7.4.179-1.fc20.i686 2/6
Upgrading : vim-enhanced-2:7.4.179-1.fc20.i686 3/6
...

Upgraded:
vim-X11.i686 2:7.4.179-1.fc20 vim-common.i686 2:7.4.179-1.fc20
vim-enhanced.i686 2:7.4.179-1.fc20

Calling tracer
vim-X11

Done!


## Feedback
Please report any bugs or feature requests right [here](https://github.com/FrostyX/tracer/issues) on GitHub. Pull requests are also welcome. If you rather want a talk or something, you can find me on `#gentoo.cs` or `fedora-cs` `@freenode` or you can [mail me](mailto:[email protected]).
Please report any bugs or feature requests to [issues](https://github.com/FrostyX/tracer/issues) on this repository. Pull requests are also welcome. If you rather want a talk or something, you can find me on `#gentoo.cs` or `#fedora-cs` `@freenode` or you can [mail me](mailto:[email protected]).
89 changes: 28 additions & 61 deletions bin/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,33 @@

# Enable importing modules from parent directory (tracer's root directory)
import os
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parentdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
os.sys.path.insert(0, parentdir)

# System modules
import re
import psutil
import platform

# Tracer modules
from packageManagers.yum import Yum
from packageManagers.portage import Portage
import resources.memory as memory

# Returns instance of package manager according to installed linux distribution
def package_manager():
def e(): raise OSError("Unknown or unsupported linux distribution")

distro = platform.linux_distribution(full_distribution_name=False)[0]
return {
'gentoo': Portage(),
'fedora': Yum(),
}.get(distro, e)

PACKAGE_MANAGER = package_manager()

# Returns list of packages what tracer should care about
def modified_packages():
# Else branch is only for dev and testing purposes
# Use: if True or if not True
if True:
packages = PACKAGE_MANAGER.packages_newer_than(psutil.BOOT_TIME)
else:
# Lets say these packages were updated
packages = [
{'name': 'xterm'},
{'name': 'ark'},
{'name': 'kactivities'},
]
return packages

# Returns list of packages which have some files loaded in memory
def trace_running():
"""
Returns list of package names which owns outdated files loaded in memory
@TODO This function should be hardly optimized
"""

files_in_memory = memory.files_in_memory()
packages = modified_packages()

modified = []
for package in packages:
for file in PACKAGE_MANAGER.package_files(package['name']):
# Doesnt matter what is after dot cause in package files there is version number after it
regex = re.compile('^' + re.escape(file) + "(\.*|$)")
if memory.is_in_memory(regex, files_in_memory):
modified.append(package['name'])
break
return modified

# More times a package is updated the more times it is contained in a package list.
for package in set(trace_running()):
print package
import sys
import time
from resources.tracer import Tracer
from resources.args_parser import args
from resources.package import Package


def main(argv=sys.argv, stdin=[]):
# If there is something on stdin (that means piped into tracer)
stdin_packages = []
if not sys.stdin.isatty():
stdin_packages = sys.stdin.readline().split()

# All input packages enchanced by actual time (as modified time)
packages = []
for package in args.packages + stdin_packages:
packages.append(Package(package, time.time() if args.now else None))

tracer = Tracer()
tracer.specified_packages = packages
tracer.now = args.now
for package in set(tracer.trace_running()):
# More times a package is updated the more times it is contained in a package list.
print package.name

if __name__ == '__main__':
main()
58 changes: 58 additions & 0 deletions integration/dnf/plugins/tracer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#-*- coding: utf-8 -*-
# tracer.py, calls tracer after every successful transaction.
# Also supplies the 'tracer' command.
#
# Copyright (C) 2014 Jakub Kadlčík
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

import dnf
import subprocess

class Tracer(dnf.Plugin):
"""DNF plugin for `tracer` command"""
name = 'tracer'

def __init__(self, base, cli):
self.base = base
self.cli = cli
if self.cli is not None:
self.cli.register_command(TracerCommand)

def transaction(self):
"""
Call after successful transaction
Warning, this code uses undocummented parts. See https://bugzilla.redhat.com/show_bug.cgi?id=1067156
"""
items = []
for transaction_item in self.base.transaction:
item = transaction_item.installed.name if transaction_item.installed else transaction_item.erased.name
items.append(item)

args = ['tracer', '-n'] + items
p = subprocess.Popen(args, stdout=subprocess.PIPE)
out, err = p.communicate()

print 'Calling tracer'
print out


class TracerCommand(dnf.cli.Command):
"""DNF tracer plugin"""
aliases = ['tracer']
activate_sack = True

def run(self, args):
"Call after running `dnf tracer ...`"
for arg in args:
print arg
10 changes: 10 additions & 0 deletions packageManagers/dnf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#-*- coding: utf-8 -*-
"""Module to work with DNF package manager class
Copyright 2013 Jakub Kadlčík"""

from rpm import Rpm

class Dnf(Rpm):

@property
def history_path(self): return '/var/lib/dnf/history/'
5 changes: 4 additions & 1 deletion packageManagers/ipackageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

class IPackageManager:
def packages_newer_than(self, unix_time):
"""Returns list of packages which were modified between unix_time and present"""
"""
Returns list of packages which were modified between unix_time and present
Packages in list should be dictionaries with keys {"name", "modified"}
"""
raise NotImplementedError

def package_files(self, pkg_name):
Expand Down
9 changes: 6 additions & 3 deletions packageManagers/portage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
Copyright 2013 Jakub Kadlčík"""

from ipackageManager import IPackageManager
from resources.package import Package
import subprocess
import time
import os

class Portage(IPackageManager):

Expand All @@ -22,15 +24,16 @@ def packages_newer_than(self, unix_time):
# There actually should be %e instead of %d
modified = time.mktime(time.strptime(package[0], "%a %b %d %H:%M:%S %Y"))
if modified >= unix_time:
pkg_name = package[1]
pkg_name = package[1] # Package name with version, let's cut it off
pkg_name = pkg_name[:pkg_name.index('.')] # Cut from first . to end
pkg_name = pkg_name[:pkg_name.rindex('-')] # Cut from last - to end
newer.append({"name":pkg_name})
newer.append(Package(pkg_name, modified))
return newer

def package_files(self, pkg_name):
"""Returns list of files provided by package"""
p = subprocess.Popen(['equery', '-q', 'f', pkg_name], stdout=subprocess.PIPE)
FNULL = open(os.devnull, 'w')
p = subprocess.Popen(['equery', '-q', 'f', pkg_name], stdout=subprocess.PIPE, stderr=FNULL)
files, err = p.communicate()
return files.split('\n')[:-1]

79 changes: 79 additions & 0 deletions packageManagers/rpm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#-*- coding: utf-8 -*-
"""Base RPM package manager class
Copyright 2013 Jakub Kadlčík"""

from os import listdir
from ipackageManager import IPackageManager
from resources.package import Package
import sqlite3
import subprocess
import re

class Rpm(IPackageManager):

def packages_newer_than(self, unix_time):
"""
Returns list of packages which were modified between unix_time and present
Requires root permissions.
"""

sql = """
SELECT pkgtups.name
FROM trans_data_pkgs JOIN pkgtups ON trans_data_pkgs.pkgtupid=pkgtups.pkgtupid
WHERE trans_data_pkgs.tid = ?
ORDER BY pkgtups.pkgtupid
"""

packages = []
sqlite = self._database_file()
conn = sqlite3.connect(sqlite)
conn.row_factory = sqlite3.Row
c = conn.cursor()

for t in self._transactions_newer_than(unix_time):
c.execute(sql, [t['tid']])
for p in c.fetchall():
packages.append(Package(p['name'], t['end']))

return packages

def package_files(self, pkg_name):
"""
Returns list of files provided by package
See also: http://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch04s02s03.html
"""

p = subprocess.Popen(['rpm', '-ql', pkg_name], stdout=subprocess.PIPE)
files, err = p.communicate()
return files.split('\n')[:-1]

def _transactions_newer_than(self, unix_time):
"""
Returns list of transactions which ran between unix_time and present.
Requires root permissions.
"""

sql = """
SELECT trans_beg.tid, trans_beg.timestamp AS beg, trans_end.timestamp AS end
FROM trans_beg JOIN trans_end ON trans_beg.tid=trans_end.tid
WHERE beg > ?
ORDER BY trans_beg.tid
"""

sqlite = self._database_file()
conn = sqlite3.connect(sqlite)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute(sql, [unix_time])
return c.fetchall()

def _database_file(self):
"""
Returns path to yum history database file
Requires root permissions.
"""

for file in listdir(self.history_path):
if file.startswith("history-") and file.endswith(".sqlite"):
return self.history_path + file

Loading

0 comments on commit 59a9142

Please sign in to comment.