7
7
import logging
8
8
from collections import Counter
9
9
from dataclasses import dataclass
10
+ from typing import Optional
10
11
11
12
from pants .engine .internals .scheduler import Workunit
12
13
from pants .engine .rules import collect_rules , rule
17
18
WorkunitsCallbackFactoryRequest ,
18
19
)
19
20
from pants .engine .unions import UnionRule
20
- from pants .option .option_types import BoolOption
21
+ from pants .option .option_types import BoolOption , StrOption
21
22
from pants .option .subsystem import Subsystem
22
23
from pants .util .collections import deep_getsizeof
23
24
from pants .util .strutil import softwrap
@@ -55,13 +56,30 @@ class StatsAggregatorSubsystem(Subsystem):
55
56
),
56
57
advanced = True ,
57
58
)
59
+ output_file = StrOption (
60
+ default = None ,
61
+ metavar = "<path>" ,
62
+ help = "Output the stats to this file. If unspecified, outputs to stdout." ,
63
+ )
64
+
65
+
66
+ def log_or_write_to_file (output_file : Optional [str ], text : str , logger : logging .Logger ) -> None :
67
+ """Send text to the stdout or write to the output file."""
68
+ if output_file :
69
+ with open (output_file , "w" ) as fh :
70
+ fh .write (text )
71
+ else :
72
+ logger .info (text )
58
73
59
74
60
75
class StatsAggregatorCallback (WorkunitsCallback ):
61
- def __init__ (self , * , log : bool , memory : bool , has_histogram_module : bool ) -> None :
76
+ def __init__ (
77
+ self , * , log : bool , memory : bool , output_file : Optional [str ], has_histogram_module : bool
78
+ ) -> None :
62
79
super ().__init__ ()
63
80
self .log = log
64
81
self .memory = memory
82
+ self .output_file = output_file
65
83
self .has_histogram_module = has_histogram_module
66
84
67
85
@property
@@ -80,6 +98,8 @@ def __call__(
80
98
if not finished :
81
99
return
82
100
101
+ output_contents = ""
102
+
83
103
if self .log :
84
104
# Capture global counters.
85
105
counters = Counter (context .get_metrics ())
@@ -93,7 +113,7 @@ def __call__(
93
113
counter_lines = "\n " .join (
94
114
f" { name } : { count } " for name , count in sorted (counters .items ())
95
115
)
96
- logger . info ( f"Counters:\n { counter_lines } " )
116
+ output_contents += f"Counters:\n { counter_lines } "
97
117
98
118
if self .memory :
99
119
ids : set [int ] = set ()
@@ -115,18 +135,23 @@ def __call__(
115
135
memory_lines = "\n " .join (
116
136
f" { size } \t \t { count } \t \t { name } " for size , count , name in sorted (entries )
117
137
)
118
- logger .info (f"Memory summary (total size in bytes, count, name):\n { memory_lines } " )
138
+ output_contents += (
139
+ f"\n Memory summary (total size in bytes, count, name):\n { memory_lines } "
140
+ )
119
141
120
142
if not (self .log and self .has_histogram_module ):
143
+ log_or_write_to_file (self .output_file , output_contents , logger )
121
144
return
145
+
122
146
from hdrh .histogram import HdrHistogram # pants: no-infer-dep
123
147
124
148
histograms = context .get_observation_histograms ()["histograms" ]
125
149
if not histograms :
126
- logger .info ("No observation histogram were recorded." )
150
+ output_contents += "\n No observation histogram were recorded."
151
+ log_or_write_to_file (self .output_file , output_contents , logger )
127
152
return
128
153
129
- logger . info ( "Observation histogram summaries:")
154
+ output_contents += " \n Observation histogram summaries:"
130
155
for name , encoded_histogram in histograms .items ():
131
156
# Note: The Python library for HDR Histogram will only decode compressed histograms
132
157
# that are further encoded with base64. See
@@ -138,8 +163,8 @@ def __call__(
138
163
[25 , 50 , 75 , 90 , 95 , 99 ]
139
164
).items ()
140
165
)
141
- logger . info (
142
- f"Summary of `{ name } ` observation histogram:\n "
166
+ output_contents += (
167
+ f"\n Summary of `{ name } ` observation histogram:\n "
143
168
f" min: { histogram .get_min_value ()} \n "
144
169
f" max: { histogram .get_max_value ()} \n "
145
170
f" mean: { histogram .get_mean_value ():.3f} \n "
@@ -148,6 +173,7 @@ def __call__(
148
173
f" sum: { int (histogram .get_mean_value () * histogram .total_count )} \n "
149
174
f"{ percentile_to_vals } "
150
175
)
176
+ log_or_write_to_file (self .output_file , output_contents , logger )
151
177
152
178
153
179
@dataclass (frozen = True )
@@ -178,6 +204,7 @@ def construct_callback(
178
204
StatsAggregatorCallback (
179
205
log = subsystem .log ,
180
206
memory = subsystem .memory_summary ,
207
+ output_file = subsystem .output_file ,
181
208
has_histogram_module = has_histogram_module ,
182
209
)
183
210
if subsystem .log or subsystem .memory_summary
0 commit comments