1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2015 - 2020, Phoronix Media
7	Copyright (C) 2015 - 2020, Michael Larabel
8
9	This program is free software; you can redistribute it and/or modify
10	it under the terms of the GNU General Public License as published by
11	the Free Software Foundation; either version 3 of the License, or
12	(at your option) any later version.
13
14	This program is distributed in the hope that it will be useful,
15	but WITHOUT ANY WARRANTY; without even the implied warranty of
16	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17	GNU General Public License for more details.
18
19	You should have received a copy of the GNU General Public License
20	along with this program. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23class linux_perf extends pts_module_interface
24{
25	const module_name = 'Linux Perf Framework Reporter';
26	const module_version = '1.1.0';
27	const module_description = 'Setting LINUX_PERF=1 will auto-load and enable this Phoronix Test Suite module. The module also depends upon running a modern Linux kernel (supporting perf) and that the perf binary is available via standard system paths.';
28	const module_author = 'Michael Larabel';
29
30	private static $result_identifier;
31	private static $successful_test_run;
32	private static $std_output;
33	private static $tmp_file;
34
35	public static function module_environmental_variables()
36	{
37		return array('LINUX_PERF');
38	}
39	public static function module_info()
40	{
41		return null;
42	}
43	public static function __run_manager_setup(&$test_run_manager)
44	{
45		// Verify LINUX_PERF is set, `perf` can be found, and is Linux
46		if(getenv('LINUX_PERF') == 0 || !pts_client::executable_in_path('perf') || !phodevi::is_linux())
47		{
48			return pts_module::MODULE_UNLOAD; // This module doesn't have anything else to do
49		}
50		echo PHP_EOL . 'Linux PERF Monitoring Enabled.' . PHP_EOL . PHP_EOL;
51
52		// This module won't be too useful if you're not saving the results to see the graphs
53		$test_run_manager->force_results_save();
54	}
55	public static function __pre_run_process(&$test_run_manager)
56	{
57		// Copy the current result identifier
58		self::$result_identifier = $test_run_manager->get_results_identifier();
59	}
60	public static function __pre_test_run(&$test_run_request)
61	{
62		// Set the perf command to pass in front of all tests to run
63		self::$tmp_file = tempnam(sys_get_temp_dir(), 'perf');
64		// -d or below is more exhaustive list
65		$test_run_request->exec_binary_prepend = 'perf stat -e branches,branch-misses,cache-misses,cache-references,cycles,instructions,cs,cpu-clock,page-faults,duration_time,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-prefetches,L1-icache-load-misses,context-switches,cpu-migrations,branch-loads,branch-load-misses,dTLB-loads,dTLB-load-misses,iTLB-load-misses,iTLB-loads -o ' . self::$tmp_file . ' ';
66	}
67	public static function __post_test_run_success($test_run_request)
68	{
69		// Base the new result object/graph off of what just ran
70		self::$successful_test_run = clone $test_run_request;
71
72		// For now the current implementation is just copying the perf output for the last test run, but rather easily could be adapted to take average of all test runs, etc
73		//self::$std_output = $test_run_request->test_result_standard_output;
74		self::$std_output = file_get_contents(self::$tmp_file);
75		pts_file_io::unlink(self::$tmp_file);
76	}
77	public static function __post_test_run_process(&$result_file)
78	{
79		if(self::$successful_test_run && !empty(self::$std_output))
80		{
81			if(($x = strpos(self::$std_output, 'Performance counter stats for')) === 0)
82			{
83				// No output found
84				return;
85			}
86			self::$std_output = substr(self::$std_output, $x);
87
88			// Items to find and report from the perf output
89			$perf_stats = array(
90				'page-faults' => array('Page Faults', 'Faults', 'LIB'),
91				'context-switches' => array('Context Switches', 'Context Switches', 'LIB'),
92				'cpu-migrations' => array('CPU Migrations', 'CPU Migrations', 'LIB'),
93				'branches' => array('Branches', 'Branches', ''),
94				'branch-misses' => array('Branch Misses', 'Branch Misses', 'LIB'),
95				'seconds user' => array('User Time', 'Seconds', 'LIB'),
96				'seconds sys' => array('Kernel/System Time', 'Seconds', 'LIB'),
97				'stalled-cycles-frontend' => array('Stalled Cycles Front-End', 'Cycles Idle', 'LIB'),
98				'stalled-cycles-backend' => array('Stalled Cycles Back-End', 'Cycles Idle', 'LIB'),
99				'L1-dcache-loads' => array('L1d Loads', 'L1d Cache Loads', ''),
100				'L1-icache-loads' => array('L1i Loads', 'L1i Cache Loads', ''),
101				'L1-dcache-load-misses' => array('L1d Load Misses', 'L1 Data Cache Load Misses', 'LIB'),
102				'L1-icache-load-misses' => array('L1i Load Misses', 'L1 Instruction Cache Load Misses', 'LIB'),
103				'cache-misses' => array('Cache Misses', 'Cache Misses', 'LIB'),
104				'branch-load-misses' => array('Branch Load Misses', 'Branch Load Misses', 'LIB'),
105				'dTLB-load-misses' => array('dTLB Load Misses', 'dTLB Load Misses', 'LIB'),
106				'ex_ret_mmx_fp_instr.sse_instr' => array('SSE Instructions', 'SSE Instructions', ''),
107				'fp_ret_sse_avx_ops.all' => array('SSE+AVX Instructions', 'AVX Instructions', ''),
108				'instructions' => array('Instructions', 'Instructions', 'LIB'),
109				);
110
111			foreach($perf_stats as $string_to_match => $data)
112			{
113				list($pretty_string, $units, $hib_or_lib) = $data;
114				if(($x = strpos(self::$std_output, $string_to_match)) !== false)
115				{
116					$sout = substr(self::$std_output, 0, $x);
117					$sout = str_replace(',', '', trim(substr($sout, (strrpos($sout, PHP_EOL) + 1))));
118
119					if(is_numeric($sout) && $sout > 0)
120					{
121						// Assemble the new result object for the matching perf item
122						$original_parent_hash = self::$successful_test_run->get_comparison_hash(true, false);
123						$test_result = clone self::$successful_test_run;
124						$test_result->test_profile->set_identifier(null);
125						$test_result->set_parent_hash($original_parent_hash);
126
127						// Description to show on graph
128						$test_result->set_used_arguments_description($pretty_string . ' (' . $test_result->get_arguments_description() . ')');
129
130						// Make a unique string for XML result matching
131						$test_result->set_used_arguments('perf ' . $string_to_match . ' ' . $test_result->get_arguments());
132						$test_result->test_profile->set_result_scale($units);
133						$test_result->test_profile->set_result_proportion($hib_or_lib);
134						$test_result->test_result_buffer = new pts_test_result_buffer();
135						$test_result->test_result_buffer->add_test_result(self::$result_identifier, $sout);
136						$test_result->set_parent_hash(self::$successful_test_run->get_comparison_hash(true, false));
137						$result_file->add_result($test_result);
138					}
139				}
140			}
141		}
142
143		// Reset to be safe
144		self::$successful_test_run = null;
145		self::$std_output = null;
146	}
147}
148?>
149