Skip to content

Commit b32a075

Browse files
committed
Make sure that instrumentation of line and instructions at the same time is supported.
1 parent 0be1562 commit b32a075

File tree

2 files changed

+183
-17
lines changed

2 files changed

+183
-17
lines changed

Lib/test/test_monitoring.py

+162
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,168 @@ def func3():
767767
('line', 'check_events', 11),
768768
('call', 'set_events', 2)])
769769

770+
class InstructionRecorder:
771+
772+
event_type = E.INSTRUCTION
773+
774+
def __init__(self, events):
775+
self.events = events
776+
777+
def __call__(self, code, offset):
778+
# Filter out instructions in check_events to lower noise
779+
if code.co_name != "check_events":
780+
self.events.append(("instruction", code.co_name, offset))
781+
782+
783+
LINE_AND_INSTRUCTION_RECORDERS = InstructionRecorder, LineRecorder
784+
785+
class TestLineAndInstructionEvents(CheckEvents):
786+
maxDiff = None
787+
788+
def test_simple(self):
789+
790+
def func1():
791+
line1 = 1
792+
line2 = 2
793+
line3 = 3
794+
795+
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
796+
('line', 'check_events', 10),
797+
('instruction', 'func1', 1),
798+
('line', 'func1', 1),
799+
('instruction', 'func1', 2),
800+
('instruction', 'func1', 3),
801+
('line', 'func1', 2),
802+
('instruction', 'func1', 4),
803+
('instruction', 'func1', 5),
804+
('line', 'func1', 3),
805+
('instruction', 'func1', 6),
806+
('instruction', 'func1', 7),
807+
('line', 'check_events', 11)])
808+
809+
def test_c_call(self):
810+
811+
def func2():
812+
line1 = 1
813+
[].append(2)
814+
line3 = 3
815+
816+
self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
817+
('line', 'check_events', 10),
818+
('instruction', 'func2', 1),
819+
('line', 'func2', 1),
820+
('instruction', 'func2', 2),
821+
('instruction', 'func2', 3),
822+
('line', 'func2', 2),
823+
('instruction', 'func2', 4),
824+
('instruction', 'func2', 14),
825+
('instruction', 'func2', 15),
826+
('instruction', 'func2', 20),
827+
('instruction', 'func2', 21),
828+
('line', 'func2', 3),
829+
('instruction', 'func2', 22),
830+
('instruction', 'func2', 23),
831+
('line', 'check_events', 11)])
832+
833+
def test_try_except(self):
834+
835+
def func3():
836+
try:
837+
line = 2
838+
raise KeyError
839+
except:
840+
line = 5
841+
line = 6
842+
843+
self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
844+
('line', 'check_events', 10),
845+
('instruction', 'func3', 1),
846+
('line', 'func3', 1),
847+
('instruction', 'func3', 2),
848+
('line', 'func3', 2),
849+
('instruction', 'func3', 3),
850+
('instruction', 'func3', 4),
851+
('line', 'func3', 3),
852+
('instruction', 'func3', 9),
853+
('instruction', 'func3', 10),
854+
('instruction', 'func3', 11),
855+
('line', 'func3', 4),
856+
('instruction', 'func3', 12),
857+
('line', 'func3', 5),
858+
('instruction', 'func3', 13),
859+
('instruction', 'func3', 14),
860+
('instruction', 'func3', 15),
861+
('line', 'func3', 6),
862+
('instruction', 'func3', 16),
863+
('instruction', 'func3', 17),
864+
('line', 'check_events', 11)])
865+
866+
class TestInstallIncrementallly(unittest.TestCase):
867+
868+
def check_events(self, func, must_include, tool=TEST_TOOL, recorders=(ExceptionRecorder,)):
869+
try:
870+
self.assertEqual(sys.monitoring._all_events(), {})
871+
event_list = []
872+
all_events = 0
873+
for recorder in recorders:
874+
all_events |= recorder.event_type
875+
sys.monitoring.set_events(tool, all_events)
876+
for recorder in recorders:
877+
sys.monitoring.register_callback(tool, recorder.event_type, recorder(event_list))
878+
func()
879+
sys.monitoring.set_events(tool, 0)
880+
for recorder in recorders:
881+
sys.monitoring.register_callback(tool, recorder.event_type, None)
882+
for line in must_include:
883+
self.assertIn(line, event_list)
884+
finally:
885+
sys.monitoring.set_events(tool, 0)
886+
for recorder in recorders:
887+
sys.monitoring.register_callback(tool, recorder.event_type, None)
888+
889+
@staticmethod
890+
def func1():
891+
line1 = 1
892+
893+
MUST_INCLUDE_LI = [
894+
('instruction', 'func1', 1),
895+
('line', 'func1', 1),
896+
('instruction', 'func1', 2),
897+
('instruction', 'func1', 3)]
898+
899+
def test_line_then_instruction(self):
900+
recorders = [ LineRecorder, InstructionRecorder ]
901+
self.check_events(self.func1,
902+
recorders = recorders, must_include = self.EXPECTED_LI)
903+
904+
def test_instruction_then_line(self):
905+
recorders = [ InstructionRecorder, LineRecorderLowNoise ]
906+
self.check_events(self.func1,
907+
recorders = recorders, must_include = self.EXPECTED_LI)
908+
909+
@staticmethod
910+
def func2():
911+
len(())
912+
913+
MUST_INCLUDE_CI = [
914+
('instruction', 'func2', 1),
915+
('call', 'func2', sys.monitoring.MISSING),
916+
('call', 'len', ()),
917+
('instruction', 'func2', 6),
918+
('instruction', 'func2', 7)]
919+
920+
921+
922+
def test_line_then_instruction(self):
923+
recorders = [ CallRecorder, InstructionRecorder ]
924+
self.check_events(self.func2,
925+
recorders = recorders, must_include = self.MUST_INCLUDE_CI)
926+
927+
def test_instruction_then_line(self):
928+
recorders = [ InstructionRecorder, CallRecorder ]
929+
self.check_events(self.func2,
930+
recorders = recorders, must_include = self.MUST_INCLUDE_CI)
931+
770932
class TestLocalEvents(unittest.TestCase):
771933

772934
def check_events(self, func, expected, tool=TEST_TOOL, recorders=(ExceptionRecorder,)):

Python/instrumentation.c

+21-17
Original file line numberDiff line numberDiff line change
@@ -652,24 +652,26 @@ static void
652652
instrument(PyCodeObject *code, int i)
653653
{
654654
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
655-
int opcode = _Py_OPCODE(*instr);
656-
/* TO DO -- handle INSTRUMENTED_INSTRUCTION */
657-
CHECK(opcode != INSTRUMENTED_INSTRUCTION);
655+
uint8_t *opcode_ptr = &instr->op.code;
656+
int opcode =*opcode_ptr;
657+
if (opcode == INSTRUMENTED_INSTRUCTION) {
658+
opcode_ptr = &code->_co_monitoring->per_instruction_opcodes[i];
659+
opcode = *opcode_ptr;
660+
}
658661
if (opcode == INSTRUMENTED_LINE) {
659662
_PyCoLineInstrumentationData *lines = &code->_co_monitoring->lines[i];
660-
opcode = lines->original_opcode;
661-
CHECK(opcode != 0);
663+
opcode_ptr = &lines->original_opcode;
664+
opcode = *opcode_ptr;
662665
CHECK(!is_instrumented(opcode));
663666
CHECK(opcode == _PyOpcode_Deopt[opcode]);
664-
lines->original_opcode = INSTRUMENTED_OPCODES[opcode];
665-
CHECK(lines->original_opcode != 0);
666667
}
667-
else if (!is_instrumented(opcode)) {
668-
opcode = _PyOpcode_Deopt[opcode];
669-
int instrumented = INSTRUMENTED_OPCODES[opcode];
668+
CHECK(opcode != 0);
669+
if (!is_instrumented(opcode)) {
670+
int deopt = _PyOpcode_Deopt[opcode];
671+
int instrumented = INSTRUMENTED_OPCODES[deopt];
670672
assert(instrumented);
671-
instr->op.code = instrumented;
672-
if (_PyOpcode_Caches[opcode]) {
673+
*opcode_ptr = instrumented;
674+
if (_PyOpcode_Caches[deopt]) {
673675
instr[1].cache = adaptive_counter_warmup();
674676
}
675677
}
@@ -678,10 +680,12 @@ instrument(PyCodeObject *code, int i)
678680
static void
679681
instrument_line(PyCodeObject *code, int i)
680682
{
681-
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
682-
int opcode = _Py_OPCODE(*instr);
683-
/* TO DO -- handle INSTRUMENTED_INSTRUCTION */
684-
CHECK(opcode != INSTRUMENTED_INSTRUCTION);
683+
uint8_t *opcode_ptr = &_PyCode_CODE(code)[i].op.code;
684+
int opcode =*opcode_ptr;
685+
if (opcode == INSTRUMENTED_INSTRUCTION) {
686+
opcode_ptr = &code->_co_monitoring->per_instruction_opcodes[i];
687+
opcode = *opcode_ptr;
688+
}
685689
if (opcode == INSTRUMENTED_LINE) {
686690
return;
687691
}
@@ -694,7 +698,7 @@ instrument_line(PyCodeObject *code, int i)
694698
);
695699
lines->original_opcode = _PyOpcode_Deopt[opcode];
696700
CHECK(lines->original_opcode > 0);
697-
instr->op.code = INSTRUMENTED_LINE;
701+
*opcode_ptr = INSTRUMENTED_LINE;
698702
}
699703

700704
static void

0 commit comments

Comments
 (0)