sapling/hgext3rd/sampling.py
Durham Goode f260c80298 configs: register more of our configs
Upstream has added devel warnings that require us to register all the configs
and remove the defaults that are specified at read time. This doesn't fix all
the cases, but is the start.

Differential Revision: https://phab.mercurial-scm.org/D1206
2017-10-23 20:02:57 -07:00

104 lines
3.7 KiB
Python

# sampling.py - sample collection extension
#
# Copyright 2016 Facebook
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# Usage:
# - This extension enhances ui.log(category, message, key=value, ...)
# to also append filtered logged events as JSON to a file.
# - The events are separated by NULL characters: '\0'.
# - The file is either specified with the SCM_SAMPLING_FILEPATH environment
# variable or the sampling.filepath configuration.
# - If the file cannot be created or accessed, fails silently
#
# The configuration details can be found in the documentation of ui.log below
from mercurial import encoding, registrar, util
import json, os
configtable = {}
configitem = registrar.configitem(configtable)
configitem('sampling', 'filepath', default='')
def _parentfolderexists(f):
return (f is not None and
os.path.exists(os.path.dirname(os.path.normpath(f))))
def _getcandidatelocation(ui):
for candidatelocation in (
encoding.environ.get("SCM_SAMPLING_FILEPATH", None),
ui.config("sampling", "filepath")):
if _parentfolderexists(candidatelocation):
return candidatelocation
return None
def uisetup(ui):
class logtofile(ui.__class__):
@classmethod
def computesamplingfilters(cls, self):
filtermap = {}
for k in ui.configitems("sampling"):
if not k[0].startswith("key."):
continue # not a key
filtermap[k[0].lstrip("key.")] = k[1]
return filtermap
def log(self, event, *msg, **opts):
"""Redirect filtered log event to a sampling file
The configuration looks like:
[sampling]
filepath = path/to/file
key.eventname = value
key.eventname2 = value2
If an event name appears in the config, it is logged to the
samplingfile augmented with value stored as ref.
Example:
[sampling]
filepath = path/to/file
key.perfstatus = perf_status
Assuming that we call:
ui.log('perfstatus', t=3)
ui.log('perfcommit', t=3)
ui.log('perfstatus', t=42)
Then we will log in path/to/file, two JSON strings separated by \0
one for each perfstatus, like:
{"event":"perfstatus",
"ref":"perf_status",
"msg":"",
"opts":{"t":3}}\0
{"event":"perfstatus",
"ref":"perf_status",
"msg":"",
"opts":{"t":42}}\0
"""
if not util.safehasattr(self, 'samplingfilters'):
self.samplingfilters = logtofile.computesamplingfilters(self)
if event not in self.samplingfilters:
return super(logtofile, self).log(event, *msg, **opts)
ref = self.samplingfilters[event]
script = _getcandidatelocation(ui)
if script:
try:
opts["metrics_type"] = event
if msg:
# ui.log treats msg as a format string + format args.
opts["msg"] = msg[0] % msg[1:]
with open(script, 'a') as outfile:
outfile.write(json.dumps({"data": opts,
"category": ref}))
outfile.write("\0")
except EnvironmentError:
pass
return super(logtofile, self).log(event, *msg, **opts)
# Replace the class for this instance and all clones created from it:
ui.__class__ = logtofile