Merge lp:~lucio.torre/txstatsd/distinct-plugin-fix-http into lp:~txstatsd-dev/txstatsd/distinct-plugin

Proposed by Lucio Torre
Status: Merged
Approved by: Sidnei da Silva
Approved revision: 12
Merged at revision: 12
Proposed branch: lp:~lucio.torre/txstatsd/distinct-plugin-fix-http
Merge into: lp:~txstatsd-dev/txstatsd/distinct-plugin
Diff against target: 294 lines (+132/-15)
6 files modified
bin/start-database.sh (+2/-1)
bin/stop-database.sh (+2/-0)
distinctdb/distinctmetric.py (+9/-5)
distinctdb/tests/test_distinct.py (+114/-4)
distinctdb/version.py (+1/-1)
setup.py (+4/-4)
To merge this branch: bzr merge lp:~lucio.torre/txstatsd/distinct-plugin-fix-http
Reviewer Review Type Date Requested Status
Sidnei da Silva Approve
Review via email: mp+103747@code.launchpad.net

Commit message

1- makes it compatible with pg9.1 (the one in P)
2- fixes setup.py and friends, its now installable
3- fixes resource http argument parsing so we get an int out of the query args
4- fixes the get_top query so we get the correct number of hits per user
5- returns http:500 on errors

Description of the change

1- makes it compatible with pg9.1 (the one in P)
2- fixes setup.py and friends, its now installable
3- fixes resource http argument parsing so we get an int out of the query args
4- fixes the get_top query so we get the correct number of hits per user
5- returns http:500 on errors

Sorry about doing big integration tests, but it helped a lot to find the issues.

To post a comment you must log in.
Revision history for this message
Sidnei da Silva (sidnei) wrote :

Looks good.

review: Approve
Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :

The attempt to merge lp:~lucio.torre/txstatsd/distinct-plugin-fix-http into lp:~txstatsd-dev/txstatsd/distinct-plugin failed. Below is the output from the failed tests.

./bin/start-database.sh
## Starting postgres in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1 ##
The files belonging to this database system will be owned by user "tarmac".
This user must also own the server process.

The database cluster will be initialized with locale C.
The default text search configuration will be set to "english".

fixing permissions on existing directory /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 28MB
creating configuration files ... ok
creating template1 database in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
vacuuming database template1 ... ok
copying template1 to template0 ... ok
copying template1 to postgres ... ok

Success. You can now start the database server using:

    /usr/lib/postgresql/8.4/bin/postgres -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data
or
    /usr/lib/postgresql/8.4/bin/pg_ctl -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data -l logfile start

waiting for server to start.... done
server started
CREATE ROLE
To set your environment so psql will connect to this DB instance type:
    export PGHOST=/mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1
## Done. ##
psql -h `pwd`/tmp/db1 -d distinct < bin/schema.sql
CREATE TABLE
CREATE TABLE
CREATE INDEX
./bin/start-redis.sh

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the -A option the
next time you run initdb.
ERROR: role "client" already exists
NOTICE: CREATE TABLE will create implicit sequence "paths_id_seq" for serial column "paths.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "paths_pkey" for table "paths"
NOTICE: CREATE TABLE / UNIQUE will create implicit index "paths_path_key" for table "paths"
/sbin/start-stop-daemon: stat /usr/bin/redis-server: No such file or directory (No such file or directory)
make: *** [start-redis] Error 2

Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :
Download full text (4.6 KiB)

The attempt to merge lp:~lucio.torre/txstatsd/distinct-plugin-fix-http into lp:~txstatsd-dev/txstatsd/distinct-plugin failed. Below is the output from the failed tests.

./bin/start-database.sh
## Starting postgres in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1 ##
The files belonging to this database system will be owned by user "tarmac".
This user must also own the server process.

The database cluster will be initialized with locale C.
The default text search configuration will be set to "english".

fixing permissions on existing directory /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 28MB
creating configuration files ... ok
creating template1 database in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
vacuuming database template1 ... ok
copying template1 to template0 ... ok
copying template1 to postgres ... ok

Success. You can now start the database server using:

    /usr/lib/postgresql/8.4/bin/postgres -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data
or
    /usr/lib/postgresql/8.4/bin/pg_ctl -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data -l logfile start

waiting for server to start.... done
server started
CREATE ROLE
To set your environment so psql will connect to this DB instance type:
    export PGHOST=/mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1
## Done. ##
psql -h `pwd`/tmp/db1 -d distinct < bin/schema.sql
CREATE TABLE
CREATE TABLE
CREATE INDEX
./bin/start-redis.sh
trial distinctdb/
                                                                        [ERROR]

===============================================================================
[ERROR]: distinctdb.tests.test_distinct

Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/twisted/trial/runner.py", line 563, in loadPackage
    module = modinfo.load()
  File "/usr/lib/python2.6/dist-packages/twisted/python/modules.py", line 381, in load
    return self.pathEntry.pythonPath.moduleLoader(self.name)
  File "/usr/lib/python2.6/dist-packages/twisted/python/reflect.py", line 464, in namedAny
    topLevelPackage = _importAndCheckStack(trialname)
  File "/mnt/tarmac/cache/txstatsd/distinct-plugin/distinctdb/tests/test_distinct.py", line 25, in <module>
    from twisted.plugins import distinctdbplugin
  File "/mnt/tarmac/cache/txstatsd/distinct-plugin/twisted/plugins/distinctdbplugin.py", line 4, in <module>
    from txstatsd.itxstatsd import IMetricFactory
exceptions.ImportError: No module named txstatsd.itxstatsd
-------------------------------------------------------------------------------

FAILED (errors=1)

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the -A option the
next time you run initdb.
ERROR: role "client" already exists
NOT...

Read more...

Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :
Download full text (4.8 KiB)

The attempt to merge lp:~lucio.torre/txstatsd/distinct-plugin-fix-http into lp:~txstatsd-dev/txstatsd/distinct-plugin failed. Below is the output from the failed tests.

./bin/start-database.sh
## Starting postgres in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1 ##
The files belonging to this database system will be owned by user "tarmac".
This user must also own the server process.

The database cluster will be initialized with locale C.
The default text search configuration will be set to "english".

fixing permissions on existing directory /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 28MB
creating configuration files ... ok
creating template1 database in /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
vacuuming database template1 ... ok
copying template1 to template0 ... ok
copying template1 to postgres ... ok

Success. You can now start the database server using:

    /usr/lib/postgresql/8.4/bin/postgres -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data
or
    /usr/lib/postgresql/8.4/bin/pg_ctl -D /mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1/data -l logfile start

waiting for server to start.... done
server started
CREATE ROLE
To set your environment so psql will connect to this DB instance type:
    export PGHOST=/mnt/tarmac/cache/txstatsd/distinct-plugin/tmp/db1
## Done. ##
psql -h `pwd`/tmp/db1 -d distinct < bin/schema.sql
CREATE TABLE
CREATE TABLE
CREATE INDEX
./bin/start-redis.sh
trial distinctdb/
distinctdb.tests.test_distinct
  TestDatabaseMetricStorage
    test_connect ... [OK]
    test_create_metric_id ... [OK]
    test_find_saved_data ... [OK]
    test_get_distinct_count ... [OK]
    test_get_distinct_top_value ... [OK]
    test_load_metric_id ... [OK]
    test_save_bucket_twice ... [OK]
  TestDistinctMetricReporter
    test_configure ... [OK]
    test_get_bucket_no ... [OK]
    test_max ... [OK]
    test_reports ... [OK]
  TestDistinctResource
    test_render_count_resource ... [OK]
    test_render_top_resource ... [OK]
  TestDistinctResourceIntegration
    test_count ... [OK]
    test_top ... ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/start-database.sh'
--- bin/start-database.sh 2011-12-08 21:08:38 +0000
+++ bin/start-database.sh 2012-04-26 18:22:25 +0000
@@ -23,6 +23,8 @@
23 export PGBINDIR=/usr/lib/postgresql/8.4/bin23 export PGBINDIR=/usr/lib/postgresql/8.4/bin
24 elif [ -d /usr/lib/postgresql/8.3 ]; then24 elif [ -d /usr/lib/postgresql/8.3 ]; then
25 export PGBINDIR=/usr/lib/postgresql/8.3/bin25 export PGBINDIR=/usr/lib/postgresql/8.3/bin
26 elif [ -d /usr/lib/postgresql/9.1 ]; then
27 export PGBINDIR=/usr/lib/postgresql/9.1/bin
26 else28 else
27 echo "Cannot find valid parent for PGBINDIR"29 echo "Cannot find valid parent for PGBINDIR"
28 fi30 fi
@@ -34,7 +36,6 @@
34 (36 (
35 cat <<EOF37 cat <<EOF
36search_path='\$user,public,ts2'38search_path='\$user,public,ts2'
37add_missing_from=false
38log_statement='all'39log_statement='all'
39log_line_prefix='[%m] %q%u@%d %c '40log_line_prefix='[%m] %q%u@%d %c '
40fsync = off41fsync = off
4142
=== modified file 'bin/stop-database.sh'
--- bin/stop-database.sh 2011-12-08 21:08:38 +0000
+++ bin/stop-database.sh 2012-04-26 18:22:25 +0000
@@ -10,6 +10,8 @@
10 PGBINDIR=/usr/lib/postgresql/8.4/bin10 PGBINDIR=/usr/lib/postgresql/8.4/bin
11elif [ -d /usr/lib/postgresql/8.3 ]; then11elif [ -d /usr/lib/postgresql/8.3 ]; then
12 PGBINDIR=/usr/lib/postgresql/8.3/bin12 PGBINDIR=/usr/lib/postgresql/8.3/bin
13elif [ -d /usr/lib/postgresql/9.1 ]; then
14 PGBINDIR=/usr/lib/postgresql/9.1/bin
13else15else
14 echo "Cannot find valid parent for PGBINDIR"16 echo "Cannot find valid parent for PGBINDIR"
15fi17fi
1618
=== modified file 'distinctdb/distinctmetric.py'
--- distinctdb/distinctmetric.py 2012-03-15 00:32:23 +0000
+++ distinctdb/distinctmetric.py 2012-04-26 18:22:25 +0000
@@ -26,6 +26,7 @@
2626
27 def _render_json_error(self, request, result):27 def _render_json_error(self, request, result):
28 """Fetch the data, make the request return"""28 """Fetch the data, make the request return"""
29 request.setResponseCode(500)
29 request.write(json.dumps({"error": result.getErrorMessage()}))30 request.write(json.dumps({"error": result.getErrorMessage()}))
30 request.finish()31 request.finish()
3132
@@ -36,7 +37,9 @@
3637
37 def render_GET(self, request):38 def render_GET(self, request):
38 """Return the web request asynchronously."""39 """Return the web request asynchronously."""
39 d = threads.deferToThread(getattr(self.target, self.name), **request.args)40 d = threads.deferToThread(
41 getattr(self.target, self.name),
42 **dict((k, float(v[0])) for k, v in request.args.items()))
40 d.addCallback(partial(self._render_json_result, request))43 d.addCallback(partial(self._render_json_result, request))
41 d.addErrback(partial(self._render_json_error, request))44 d.addErrback(partial(self._render_json_error, request))
42 return server.NOT_DONE_YET45 return server.NOT_DONE_YET
@@ -48,8 +51,10 @@
48 def __init__(self, reporter):51 def __init__(self, reporter):
49 resource.Resource.__init__(self)52 resource.Resource.__init__(self)
50 self.reporter = reporter53 self.reporter = reporter
51 self.putChild("top", JSONMethodResource(self.reporter, "get_distinct_top_value"))54 self.putChild("top",
52 self.putChild("count", JSONMethodResource(self.reporter, "get_distinct_count"))55 JSONMethodResource(self.reporter, "get_distinct_top_value"))
56 self.putChild("count",
57 JSONMethodResource(self.reporter, "get_distinct_count"))
5358
5459
55class DistinctMetricReporter(object):60class DistinctMetricReporter(object):
@@ -216,11 +221,10 @@
216 path = self.prefix + self.name221 path = self.prefix + self.name
217 c = psycopg2.connect(self.dsn)222 c = psycopg2.connect(self.dsn)
218 cr = c.cursor()223 cr = c.cursor()
219 cr.execute("SELECT value, COUNT(value) AS cnt FROM points "224 cr.execute("SELECT value, SUM(count) AS cnt FROM points "
220 "INNER JOIN paths ON (paths.id = points.path_id) "225 "INNER JOIN paths ON (paths.id = points.path_id) "
221 "WHERE paths.path = %s AND bucket BETWEEN %s AND %s"226 "WHERE paths.path = %s AND bucket BETWEEN %s AND %s"
222 "GROUP BY value ORDER BY cnt DESC LIMIT %s", (227 "GROUP BY value ORDER BY cnt DESC LIMIT %s", (
223 path, since_bucket, until_bucket, how_many,))228 path, since_bucket, until_bucket, how_many,))
224 rows = cr.fetchall()229 rows = cr.fetchall()
225 return rows230 return rows
226
227231
=== modified file 'distinctdb/tests/test_distinct.py'
--- distinctdb/tests/test_distinct.py 2012-03-15 00:32:23 +0000
+++ distinctdb/tests/test_distinct.py 2012-04-26 18:22:25 +0000
@@ -20,11 +20,15 @@
20import redis20import redis
2121
22from twisted.trial.unittest import TestCase22from twisted.trial.unittest import TestCase
23from twisted.internet import reactor23from twisted.internet import reactor, protocol, defer
24from twisted.plugin import getPlugins24from twisted.plugin import getPlugins
25from twisted.plugins import distinctdbplugin25from twisted.plugins import distinctdbplugin
26from twisted.web.test.test_web import DummyRequest26from twisted.web.test.test_web import DummyRequest
2727
28from twisted.application import internet
29from twisted.web import server
30from twisted.web.client import Agent
31
28from txstatsd.itxstatsd import IMetricFactory32from txstatsd.itxstatsd import IMetricFactory
29from txstatsd import service33from txstatsd import service
3034
@@ -46,7 +50,7 @@
4650
47 def get_distinct_top_value(self, since, until, how_many=20):51 def get_distinct_top_value(self, since, until, how_many=20):
48 self.called.append(("get_distinct_top_value", (since, until, how_many)))52 self.called.append(("get_distinct_top_value", (since, until, how_many)))
49 return [("one", 1), ("two", 1)] 53 return [("one", 1), ("two", 1)]
5054
5155
52class TestDistinctMetricReporter(TestCase):56class TestDistinctMetricReporter(TestCase):
@@ -130,6 +134,7 @@
130 reporter = DummyReporter()134 reporter = DummyReporter()
131 request = DummyRequest([])135 request = DummyRequest([])
132 resource = distinct.JSONMethodResource(reporter, "get_foo")136 resource = distinct.JSONMethodResource(reporter, "get_foo")
137
133 def check(result):138 def check(result):
134 self.assertEquals({"result": "foo"},139 self.assertEquals({"result": "foo"},
135 json.loads("".join(request.written)))140 json.loads("".join(request.written)))
@@ -145,9 +150,11 @@
145 def test_render_top_resource(self):150 def test_render_top_resource(self):
146 reporter = DummyReporter()151 reporter = DummyReporter()
147 request = DummyRequest([])152 request = DummyRequest([])
148 request.args = {"since": time.time(), "until": time.time() + 1}153 request.args = {"since": [str(time.time())],
154 "until": [str(time.time() + 1)]}
149 resource = distinct.DistinctResource(reporter)155 resource = distinct.DistinctResource(reporter)
150 child_resource = resource.getChildWithDefault("top", request)156 child_resource = resource.getChildWithDefault("top", request)
157
151 def check(result):158 def check(result):
152 self.assertEquals(json.dumps({"result": [("one", 1), ("two", 1)]}),159 self.assertEquals(json.dumps({"result": [("one", 1), ("two", 1)]}),
153 "".join(request.written))160 "".join(request.written))
@@ -159,9 +166,11 @@
159 def test_render_count_resource(self):166 def test_render_count_resource(self):
160 reporter = DummyReporter()167 reporter = DummyReporter()
161 request = DummyRequest([])168 request = DummyRequest([])
162 request.args = {"since": time.time(), "until": time.time() + 1}169 request.args = {"since": [str(time.time())],
170 "until": [str(time.time() + 1)]}
163 resource = distinct.DistinctResource(reporter)171 resource = distinct.DistinctResource(reporter)
164 child_resource = resource.getChildWithDefault("count", request)172 child_resource = resource.getChildWithDefault("count", request)
173
165 def check(result):174 def check(result):
166 self.assertEquals(json.dumps({"result": 42}),175 self.assertEquals(json.dumps({"result": 42}),
167 "".join(request.written))176 "".join(request.written))
@@ -200,6 +209,107 @@
200 dmr._save_bucket(dmr.bucket, bucket_no)209 dmr._save_bucket(dmr.bucket, bucket_no)
201210
202211
212class ResponseCollector(protocol.Protocol):
213
214 def __init__(self, finished):
215 self.finished = finished
216 self.data = []
217
218 def dataReceived(self, bytes):
219 self.data.append(bytes)
220
221 def connectionLost(self, reason):
222 self.finished.callback("".join(self.data))
223
224
225def collect_response(response):
226 d = defer.Deferred()
227 c = ResponseCollector(d)
228 response.deliverBody(c)
229 return d
230
231
232class ErrorDummyReporter(object):
233
234 def get_distinct_count(self, *args, **kweargs):
235 raise Exception("die!")
236
237
238class TestDistinctResourceIntegrationError(TestCase):
239
240 def setUp(self):
241 dmr = ErrorDummyReporter()
242 root = distinct.DistinctResource(dmr)
243 site = server.Site(root)
244 self.port = 12341
245 self.service = internet.TCPServer(self.port, site)
246 self.service.startService()
247
248 @defer.inlineCallbacks
249 def test_count(self):
250 agent = Agent(reactor)
251
252 result = yield agent.request('GET',
253 'http://localhost:%s/count?since=0&until=1000000'
254 % (self.port,))
255 self.assertEquals(500, result.code)
256
257 def tearDown(self):
258 self.service.stopService()
259
260
261class TestDistinctResourceIntegration(TestDatabase):
262
263 def setUp(self):
264 TestDatabase.setUp(self)
265 dmr = distinct.DistinctMetricReporter("test", dsn=self.dsn)
266 root = dmr.getResource()
267 site = server.Site(root)
268 self.port = 12341
269 self.service = internet.TCPServer(self.port, site)
270 self.service.startService()
271
272 def setup_bucket(self):
273 dmr = distinct.DistinctMetricReporter("test", dsn=self.dsn)
274 dmr.update("one")
275 dmr.update("two")
276 dmr._save_bucket(dmr.bucket, 1)
277 dmr.bucket = {}
278 dmr.update("one")
279 dmr.update("one")
280 dmr._save_bucket(dmr.bucket, 2)
281
282 @defer.inlineCallbacks
283 def test_count(self):
284 self.setup_bucket()
285
286 agent = Agent(reactor)
287
288 result = yield agent.request('GET',
289 'http://localhost:%s/count?since=0&until=1000000'
290 % (self.port,))
291 self.assertEquals(200, result.code)
292 data = yield collect_response(result)
293 self.assertEquals({"result": 2}, json.loads(data))
294
295 @defer.inlineCallbacks
296 def test_top(self):
297 self.setup_bucket()
298
299 agent = Agent(reactor)
300
301 result = yield agent.request('GET',
302 'http://localhost:%s/top?since=0&until=1000000&how_many=1'
303 % (self.port,))
304 self.assertEquals(200, result.code)
305 data = yield collect_response(result)
306 self.assertEquals({"result": [['one', 3]]}, json.loads(data))
307
308 def tearDown(self):
309 self.service.stopService()
310 TestDatabase.tearDown(self)
311
312
203class TestDatabaseMetricStorage(TestDatabase):313class TestDatabaseMetricStorage(TestDatabase):
204314
205 def test_connect(self):315 def test_connect(self):
206316
=== modified file 'distinctdb/version.py'
--- distinctdb/version.py 2011-12-08 21:07:55 +0000
+++ distinctdb/version.py 2012-04-26 18:22:25 +0000
@@ -1,1 +1,1 @@
1distinctplugin = "0.0.1"1distinctdb = "0.0.1"
22
=== modified file 'setup.py'
--- setup.py 2011-12-08 21:14:57 +0000
+++ setup.py 2012-04-26 18:22:25 +0000
@@ -4,7 +4,7 @@
44
5from twisted.plugin import IPlugin, getPlugins5from twisted.plugin import IPlugin, getPlugins
66
7from distinctplugin import version7from distinctdb import version
88
9# If setuptools is present, use it to find_packages(), and also9# If setuptools is present, use it to find_packages(), and also
10# declare our dependency on epsilon.10# declare our dependency on epsilon.
@@ -20,7 +20,7 @@
20 Taken from storm setup.py.20 Taken from storm setup.py.
21 """21 """
22 packages = []22 packages = []
23 for directory, subdirectories, files in os.walk("distinctplugin"):23 for directory, subdirectories, files in os.walk("distinctdb"):
24 if '__init__.py' in files:24 if '__init__.py' in files:
25 packages.append(directory.replace(os.sep, '.'))25 packages.append(directory.replace(os.sep, '.'))
26 return packages26 return packages
@@ -42,8 +42,8 @@
4242
43setup(43setup(
44 cmdclass={'install': TxPluginInstaller},44 cmdclass={'install': TxPluginInstaller},
45 name="distinctplugin",45 name="distinctdb",
46 version=version.distinctplugin,46 version=version.distinctdb,
47 description="A txstatsd plugin for distinct counts",47 description="A txstatsd plugin for distinct counts",
48 author="txStatsD Developers",48 author="txStatsD Developers",
49 url="https://launchpad.net/txstatsd",49 url="https://launchpad.net/txstatsd",

Subscribers

People subscribed via source and target branches