mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 08:18:15 +03:00
a4d4fb3e29
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
121 lines
4.4 KiB
Python
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())
|