~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/perf/pmu-events/metric.py

Version: ~ [ linux-6.12-rc7 ] ~ [ linux-6.11.7 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.60 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.116 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.171 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.229 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.285 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.323 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.12 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

Diff markup

Differences between /tools/perf/pmu-events/metric.py (Version linux-6.12-rc7) and /tools/perf/pmu-events/metric.py (Version linux-5.14.21)


  1 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-    
  2 """Parse or generate representations of perf m    
  3 import ast                                        
  4 import decimal                                    
  5 import json                                       
  6 import re                                         
  7 from typing import Dict, List, Optional, Set,     
  8                                                   
  9                                                   
 10 class Expression:                                 
 11   """Abstract base class of elements in a metr    
 12                                                   
 13   def ToPerfJson(self) -> str:                    
 14     """Returns a perf json file encoded repres    
 15     raise NotImplementedError()                   
 16                                                   
 17   def ToPython(self) -> str:                      
 18     """Returns a python expr parseable represe    
 19     raise NotImplementedError()                   
 20                                                   
 21   def Simplify(self):                             
 22     """Returns a simplified version of self.""    
 23     raise NotImplementedError()                   
 24                                                   
 25   def Equals(self, other) -> bool:                
 26     """Returns true when two expressions are t    
 27     raise NotImplementedError()                   
 28                                                   
 29   def Substitute(self, name: str, expression:     
 30     raise NotImplementedError()                   
 31                                                   
 32   def __str__(self) -> str:                       
 33     return self.ToPerfJson()                      
 34                                                   
 35   def __or__(self, other: Union[int, float, 'E    
 36     return Operator('|', self, other)             
 37                                                   
 38   def __ror__(self, other: Union[int, float, '    
 39     return Operator('|', other, self)             
 40                                                   
 41   def __xor__(self, other: Union[int, float, '    
 42     return Operator('^', self, other)             
 43                                                   
 44   def __and__(self, other: Union[int, float, '    
 45     return Operator('&', self, other)             
 46                                                   
 47   def __rand__(self, other: Union[int, float,     
 48     return Operator('&', other, self)             
 49                                                   
 50   def __lt__(self, other: Union[int, float, 'E    
 51     return Operator('<', self, other)             
 52                                                   
 53   def __gt__(self, other: Union[int, float, 'E    
 54     return Operator('>', self, other)             
 55                                                   
 56   def __add__(self, other: Union[int, float, '    
 57     return Operator('+', self, other)             
 58                                                   
 59   def __radd__(self, other: Union[int, float,     
 60     return Operator('+', other, self)             
 61                                                   
 62   def __sub__(self, other: Union[int, float, '    
 63     return Operator('-', self, other)             
 64                                                   
 65   def __rsub__(self, other: Union[int, float,     
 66     return Operator('-', other, self)             
 67                                                   
 68   def __mul__(self, other: Union[int, float, '    
 69     return Operator('*', self, other)             
 70                                                   
 71   def __rmul__(self, other: Union[int, float,     
 72     return Operator('*', other, self)             
 73                                                   
 74   def __truediv__(self, other: Union[int, floa    
 75     return Operator('/', self, other)             
 76                                                   
 77   def __rtruediv__(self, other: Union[int, flo    
 78     return Operator('/', other, self)             
 79                                                   
 80   def __mod__(self, other: Union[int, float, '    
 81     return Operator('%', self, other)             
 82                                                   
 83                                                   
 84 def _Constify(val: Union[bool, int, float, Exp    
 85   """Used to ensure that the nodes in the expr    
 86   if isinstance(val, bool):                       
 87     return Constant(1 if val else 0)              
 88   if isinstance(val, (int, float)):               
 89     return Constant(val)                          
 90   return val                                      
 91                                                   
 92                                                   
 93 # Simple lookup for operator precedence, used     
 94 # brackets. Precedence matches that of the sim    
 95 # but differs from python where comparisons ar    
 96 # the bitwise &, ^, | but not the logical vers    
 97 # parser doesn't have.                            
 98 _PRECEDENCE = {                                   
 99     '|': 0,                                       
100     '^': 1,                                       
101     '&': 2,                                       
102     '<': 3,                                       
103     '>': 3,                                       
104     '+': 4,                                       
105     '-': 4,                                       
106     '*': 5,                                       
107     '/': 5,                                       
108     '%': 5,                                       
109 }                                                 
110                                                   
111                                                   
112 class Operator(Expression):                       
113   """Represents a binary operator in the parse    
114                                                   
115   def __init__(self, operator: str, lhs: Union    
116                rhs: Union[int, float, Expressi    
117     self.operator = operator                      
118     self.lhs = _Constify(lhs)                     
119     self.rhs = _Constify(rhs)                     
120                                                   
121   def Bracket(self,                               
122               other: Expression,                  
123               other_str: str,                     
124               rhs: bool = False) -> str:          
125     """If necessary brackets the given other v    
126                                                   
127     If ``other`` is an operator then a bracket    
128     this/self operator has higher precedence.     
129     ``other_str`` will be 'a + b'. A bracket i    
130     the bracket 'a + b * c' will evaluate 'b *    
131     * b) + c' doesn't need a bracket as 'a * b    
132     evaluated first. For 'a / (b * c)' (ie the    
133     operations) then we add the bracket to bes    
134     input, but not for '(a / b) * c' where the    
135                                                   
136     Args:                                         
137       other (Expression): is a lhs or rhs oper    
138       other_str (str): ``other`` in the approp    
139       rhs (bool):  is ``other`` on the RHS        
140                                                   
141     Returns:                                      
142       str: possibly bracketed other_str           
143     """                                           
144     if isinstance(other, Operator):               
145       if _PRECEDENCE.get(self.operator, -1) >     
146           other.operator, -1):                    
147         return f'({other_str})'                   
148       if rhs and _PRECEDENCE.get(self.operator    
149           other.operator, -1):                    
150         return f'({other_str})'                   
151     return other_str                              
152                                                   
153   def ToPerfJson(self):                           
154     return (f'{self.Bracket(self.lhs, self.lhs    
155             f'{self.Bracket(self.rhs, self.rhs    
156                                                   
157   def ToPython(self):                             
158     return (f'{self.Bracket(self.lhs, self.lhs    
159             f'{self.Bracket(self.rhs, self.rhs    
160                                                   
161   def Simplify(self) -> Expression:               
162     lhs = self.lhs.Simplify()                     
163     rhs = self.rhs.Simplify()                     
164     if isinstance(lhs, Constant) and isinstanc    
165       return Constant(ast.literal_eval(lhs + s    
166                                                   
167     if isinstance(self.lhs, Constant):            
168       if self.operator in ('+', '|') and lhs.v    
169         return rhs                                
170                                                   
171       # Simplify multiplication by 0 except fo    
172       # is deliberately introduced using this     
173       if self.operator == '*' and lhs.value ==    
174           not isinstance(rhs, Event) or 'slots    
175         return Constant(0)                        
176                                                   
177       if self.operator == '*' and lhs.value ==    
178         return rhs                                
179                                                   
180     if isinstance(rhs, Constant):                 
181       if self.operator in ('+', '|') and rhs.v    
182         return lhs                                
183                                                   
184       if self.operator == '*' and rhs.value ==    
185         return Constant(0)                        
186                                                   
187       if self.operator == '*' and self.rhs.val    
188         return lhs                                
189                                                   
190     return Operator(self.operator, lhs, rhs)      
191                                                   
192   def Equals(self, other: Expression) -> bool:    
193     if isinstance(other, Operator):               
194       return self.operator == other.operator a    
195           other.lhs) and self.rhs.Equals(other    
196     return False                                  
197                                                   
198   def Substitute(self, name: str, expression:     
199     if self.Equals(expression):                   
200       return Event(name)                          
201     lhs = self.lhs.Substitute(name, expression    
202     rhs = None                                    
203     if self.rhs:                                  
204       rhs = self.rhs.Substitute(name, expressi    
205     return Operator(self.operator, lhs, rhs)      
206                                                   
207                                                   
208 class Select(Expression):                         
209   """Represents a select ternary in the parse     
210                                                   
211   def __init__(self, true_val: Union[int, floa    
212                cond: Union[int, float, Express    
213                false_val: Union[int, float, Ex    
214     self.true_val = _Constify(true_val)           
215     self.cond = _Constify(cond)                   
216     self.false_val = _Constify(false_val)         
217                                                   
218   def ToPerfJson(self):                           
219     true_str = self.true_val.ToPerfJson()         
220     cond_str = self.cond.ToPerfJson()             
221     false_str = self.false_val.ToPerfJson()       
222     return f'({true_str} if {cond_str} else {f    
223                                                   
224   def ToPython(self):                             
225     return (f'Select({self.true_val.ToPython()    
226             f'{self.false_val.ToPython()})')      
227                                                   
228   def Simplify(self) -> Expression:               
229     cond = self.cond.Simplify()                   
230     true_val = self.true_val.Simplify()           
231     false_val = self.false_val.Simplify()         
232     if isinstance(cond, Constant):                
233       return false_val if cond.value == '0' el    
234                                                   
235     if true_val.Equals(false_val):                
236       return true_val                             
237                                                   
238     return Select(true_val, cond, false_val)      
239                                                   
240   def Equals(self, other: Expression) -> bool:    
241     if isinstance(other, Select):                 
242       return self.cond.Equals(other.cond) and     
243           other.false_val) and self.true_val.E    
244     return False                                  
245                                                   
246   def Substitute(self, name: str, expression:     
247     if self.Equals(expression):                   
248       return Event(name)                          
249     true_val = self.true_val.Substitute(name,     
250     cond = self.cond.Substitute(name, expressi    
251     false_val = self.false_val.Substitute(name    
252     return Select(true_val, cond, false_val)      
253                                                   
254                                                   
255 class Function(Expression):                       
256   """A function in an expression like min, max    
257                                                   
258   def __init__(self,                              
259                fn: str,                           
260                lhs: Union[int, float, Expressi    
261                rhs: Optional[Union[int, float,    
262     self.fn = fn                                  
263     self.lhs = _Constify(lhs)                     
264     self.rhs = _Constify(rhs)                     
265                                                   
266   def ToPerfJson(self):                           
267     if self.rhs:                                  
268       return f'{self.fn}({self.lhs.ToPerfJson(    
269     return f'{self.fn}({self.lhs.ToPerfJson()}    
270                                                   
271   def ToPython(self):                             
272     if self.rhs:                                  
273       return f'{self.fn}({self.lhs.ToPython()}    
274     return f'{self.fn}({self.lhs.ToPython()})'    
275                                                   
276   def Simplify(self) -> Expression:               
277     lhs = self.lhs.Simplify()                     
278     rhs = self.rhs.Simplify() if self.rhs else    
279     if isinstance(lhs, Constant) and isinstanc    
280       if self.fn == 'd_ratio':                    
281         if rhs.value == '0':                      
282           return Constant(0)                      
283         Constant(ast.literal_eval(f'{lhs} / {r    
284       return Constant(ast.literal_eval(f'{self    
285                                                   
286     return Function(self.fn, lhs, rhs)            
287                                                   
288   def Equals(self, other: Expression) -> bool:    
289     if isinstance(other, Function):               
290       result = self.fn == other.fn and self.lh    
291       if self.rhs:                                
292         result = result and self.rhs.Equals(ot    
293       return result                               
294     return False                                  
295                                                   
296   def Substitute(self, name: str, expression:     
297     if self.Equals(expression):                   
298       return Event(name)                          
299     lhs = self.lhs.Substitute(name, expression    
300     rhs = None                                    
301     if self.rhs:                                  
302       rhs = self.rhs.Substitute(name, expressi    
303     return Function(self.fn, lhs, rhs)            
304                                                   
305                                                   
306 def _FixEscapes(s: str) -> str:                   
307   s = re.sub(r'([^\\]),', r'\1\\,', s)            
308   return re.sub(r'([^\\])=', r'\1\\=', s)         
309                                                   
310                                                   
311 class Event(Expression):                          
312   """An event in an expression."""                
313                                                   
314   def __init__(self, name: str, legacy_name: s    
315     self.name = _FixEscapes(name)                 
316     self.legacy_name = _FixEscapes(legacy_name    
317                                                   
318   def ToPerfJson(self):                           
319     result = re.sub('/', '@', self.name)          
320     return result                                 
321                                                   
322   def ToPython(self):                             
323     return f'Event(r"{self.name}")'               
324                                                   
325   def Simplify(self) -> Expression:               
326     return self                                   
327                                                   
328   def Equals(self, other: Expression) -> bool:    
329     return isinstance(other, Event) and self.n    
330                                                   
331   def Substitute(self, name: str, expression:     
332     return self                                   
333                                                   
334                                                   
335 class Constant(Expression):                       
336   """A constant within the expression tree."""    
337                                                   
338   def __init__(self, value: Union[float, str])    
339     ctx = decimal.Context()                       
340     ctx.prec = 20                                 
341     dec = ctx.create_decimal(repr(value) if is    
342     self.value = dec.normalize().to_eng_string    
343     self.value = self.value.replace('+', '')      
344     self.value = self.value.replace('E', 'e')     
345                                                   
346   def ToPerfJson(self):                           
347     return self.value                             
348                                                   
349   def ToPython(self):                             
350     return f'Constant({self.value})'              
351                                                   
352   def Simplify(self) -> Expression:               
353     return self                                   
354                                                   
355   def Equals(self, other: Expression) -> bool:    
356     return isinstance(other, Constant) and sel    
357                                                   
358   def Substitute(self, name: str, expression:     
359     return self                                   
360                                                   
361                                                   
362 class Literal(Expression):                        
363   """A runtime literal within the expression t    
364                                                   
365   def __init__(self, value: str):                 
366     self.value = value                            
367                                                   
368   def ToPerfJson(self):                           
369     return self.value                             
370                                                   
371   def ToPython(self):                             
372     return f'Literal({self.value})'               
373                                                   
374   def Simplify(self) -> Expression:               
375     return self                                   
376                                                   
377   def Equals(self, other: Expression) -> bool:    
378     return isinstance(other, Literal) and self    
379                                                   
380   def Substitute(self, name: str, expression:     
381     return self                                   
382                                                   
383                                                   
384 def min(lhs: Union[int, float, Expression], rh    
385                                                   
386   # pylint: disable=redefined-builtin             
387   # pylint: disable=invalid-name                  
388   return Function('min', lhs, rhs)                
389                                                   
390                                                   
391 def max(lhs: Union[int, float, Expression], rh    
392                                                   
393   # pylint: disable=redefined-builtin             
394   # pylint: disable=invalid-name                  
395   return Function('max', lhs, rhs)                
396                                                   
397                                                   
398 def d_ratio(lhs: Union[int, float, Expression]    
399             rhs: Union[int, float, Expression]    
400   # pylint: disable=redefined-builtin             
401   # pylint: disable=invalid-name                  
402   return Function('d_ratio', lhs, rhs)            
403                                                   
404                                                   
405 def source_count(event: Event) -> Function:       
406   # pylint: disable=redefined-builtin             
407   # pylint: disable=invalid-name                  
408   return Function('source_count', event)          
409                                                   
410                                                   
411 def has_event(event: Event) -> Function:          
412   # pylint: disable=redefined-builtin             
413   # pylint: disable=invalid-name                  
414   return Function('has_event', event)             
415                                                   
416 def strcmp_cpuid_str(cpuid: Event) -> Function    
417   # pylint: disable=redefined-builtin             
418   # pylint: disable=invalid-name                  
419   return Function('strcmp_cpuid_str', cpuid)      
420                                                   
421 class Metric:                                     
422   """An individual metric that will specifiabl    
423   groups: Set[str]                                
424   expr: Expression                                
425   scale_unit: str                                 
426   constraint: bool                                
427                                                   
428   def __init__(self,                              
429                name: str,                         
430                description: str,                  
431                expr: Expression,                  
432                scale_unit: str,                   
433                constraint: bool = False):         
434     self.name = name                              
435     self.description = description                
436     self.expr = expr.Simplify()                   
437     # Workraound valid_only_metric hiding cert    
438     scale_unit = scale_unit.replace('/sec', '     
439     if scale_unit[0].isdigit():                   
440       self.scale_unit = scale_unit                
441     else:                                         
442       self.scale_unit = f'1{scale_unit}'          
443     self.constraint = constraint                  
444     self.groups = set()                           
445                                                   
446   def __lt__(self, other):                        
447     """Sort order."""                             
448     return self.name < other.name                 
449                                                   
450   def AddToMetricGroup(self, group):              
451     """Callback used when being added to a Met    
452     self.groups.add(group.name)                   
453                                                   
454   def Flatten(self) -> Set['Metric']:             
455     """Return a leaf metric."""                   
456     return set([self])                            
457                                                   
458   def ToPerfJson(self) -> Dict[str, str]:         
459     """Return as dictionary for Json generatio    
460     result = {                                    
461         'MetricName': self.name,                  
462         'MetricGroup': ';'.join(sorted(self.gr    
463         'BriefDescription': self.description,     
464         'MetricExpr': self.expr.ToPerfJson(),     
465         'ScaleUnit': self.scale_unit              
466     }                                             
467     if self.constraint:                           
468       result['MetricConstraint'] = 'NO_NMI_WAT    
469                                                   
470     return result                                 
471                                                   
472                                                   
473 class _MetricJsonEncoder(json.JSONEncoder):       
474   """Special handling for Metric objects."""      
475                                                   
476   def default(self, o):                           
477     if isinstance(o, Metric):                     
478       return o.ToPerfJson()                       
479     return json.JSONEncoder.default(self, o)      
480                                                   
481                                                   
482 class MetricGroup:                                
483   """A group of metrics.                          
484                                                   
485   Metric groups may be specificd on the perf c    
486   the json they aren't encoded. Metrics may be    
487   which can facilitate arrangements similar to    
488   """                                             
489                                                   
490   def __init__(self, name: str, metric_list: L    
491                                                   
492     self.name = name                              
493     self.metric_list = metric_list                
494     for metric in metric_list:                    
495       metric.AddToMetricGroup(self)               
496                                                   
497   def AddToMetricGroup(self, group):              
498     """Callback used when a MetricGroup is add    
499     for metric in self.metric_list:               
500       metric.AddToMetricGroup(group)              
501                                                   
502   def Flatten(self) -> Set[Metric]:               
503     """Returns a set of all leaf metrics."""      
504     result = set()                                
505     for x in self.metric_list:                    
506       result = result.union(x.Flatten())          
507                                                   
508     return result                                 
509                                                   
510   def ToPerfJson(self) -> str:                    
511     return json.dumps(sorted(self.Flatten()),     
512                                                   
513   def __str__(self) -> str:                       
514     return self.ToPerfJson()                      
515                                                   
516                                                   
517 class _RewriteIfExpToSelect(ast.NodeTransforme    
518   """Transformer to convert if-else nodes to S    
519                                                   
520   def visit_IfExp(self, node):                    
521     # pylint: disable=invalid-name                
522     self.generic_visit(node)                      
523     call = ast.Call(                              
524         func=ast.Name(id='Select', ctx=ast.Loa    
525         args=[node.body, node.test, node.orels    
526         keywords=[])                              
527     ast.copy_location(call, node.test)            
528     return call                                   
529                                                   
530                                                   
531 def ParsePerfJson(orig: str) -> Expression:       
532   """A simple json metric expression decoder.     
533                                                   
534   Converts a json encoded metric expression by    
535   eval routine. First tokens are mapped to Eve    
536   accidentally converted keywords or literals     
537   appropriate calls. Python's ast is used to m    
538   be handled via operator overloading. Finally    
539                                                   
540   Args:                                           
541     orig (str): String to parse.                  
542                                                   
543   Returns:                                        
544     Expression: The parsed string.                
545   """                                             
546   # pylint: disable=eval-used                     
547   py = orig.strip()                               
548   # First try to convert everything that looks    
549   # This isn't very selective so is followed u    
550   py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\    
551               r'Event(r"\1")', py)                
552   # If it started with a # it should have been    
553   py = re.sub(r'#Event\(r"([^"]*)"\)', r'Liter    
554   # Convert accidentally converted hex constan    
555   # but keep it wrapped in Event(), otherwise     
556   # a double by the Bison parser                  
557   py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\    
558   # Convert accidentally converted scientific     
559   py = re.sub(r'([0-9]+)Event\(r"(e[0-9]+)"\)'    
560   # Convert all the known keywords back from e    
561   keywords = ['if', 'else', 'min', 'max', 'd_r    
562   for kw in keywords:                             
563     py = re.sub(rf'Event\(r"{kw}"\)', kw, py)     
564   try:                                            
565     parsed = ast.parse(py, mode='eval')           
566   except SyntaxError as e:                        
567     raise SyntaxError(f'Parsing expression:\n{    
568   _RewriteIfExpToSelect().visit(parsed)           
569   parsed = ast.fix_missing_locations(parsed)      
570   return _Constify(eval(compile(parsed, orig,     
571                                                   
572                                                   
573 def RewriteMetricsInTermsOfOthers(metrics: Lis    
574                                   )-> Dict[Tup    
575   """Shorten metrics by rewriting in terms of     
576                                                   
577   Args:                                           
578     metrics (list): pmus, metric names and the    
579   Returns:                                        
580     Dict: mapping from a pmu, metric name pair    
581   """                                             
582   updates: Dict[Tuple[str, str], Expression] =    
583   for outer_pmu, outer_name, outer_expression     
584     if outer_pmu is None:                         
585       outer_pmu = 'cpu'                           
586     updated = outer_expression                    
587     while True:                                   
588       for inner_pmu, inner_name, inner_express    
589         if inner_pmu is None:                     
590           inner_pmu = 'cpu'                       
591         if inner_pmu.lower() != outer_pmu.lowe    
592           continue                                
593         if inner_name.lower() == outer_name.lo    
594           continue                                
595         if (inner_pmu, inner_name) in updates:    
596           inner_expression = updates[(inner_pm    
597         updated = updated.Substitute(inner_nam    
598       if updated.Equals(outer_expression):        
599         break                                     
600       if (outer_pmu, outer_name) in updates an    
601         break                                     
602       updates[(outer_pmu, outer_name)] = updat    
603   return updates                                  
                                                      

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php