sapling/hgext/sampling.py
Durham Goode a4d4fb3e29 metrics: rename logblockedtimes to logmeasuredtimes
Summary:
In an upcoming diff I want to add more timing measurements for various
parts of the Mercurial code (like how long status takes, vs checkout, vs
prefetch, etc). Let's rename the logblockedtimes logic to be more generic, since
it is doing basically the same thing.

Reviewed By: singhsrb

Differential Revision: D7676406

fbshipit-source-id: 9aa8c90ce562fa3ad5b654f7b3191b2c16a440c2
2018-04-18 17:05:32 -07:00

121 lines
4.4 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)
# special case: remove less interesting blocked fields starting
# with "unknown_" or "alias_".
if event == 'measuredtimes':
opts = {k: v
for k, v in opts.items()
if (not k.startswith('alias_') and not
k.startswith('unknown_'))}
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
def reposetup(ui, repo):
@repo.ui.atexit
def telemetry():
if util.safehasattr(repo, 'requirements'):
ui.log('requirements',
generaldelta=str('generaldelta' in repo.requirements).lower())
ui.log('requirements',
remotefilelog=str('remotefilelog' in repo.requirements).lower())